@vitest-agent/engine 2.1.0 → 3.0.1
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/index.d.ts +141 -37
- package/index.js +7 -4
- package/layers/ConfigLive.js +31 -4
- package/layers/DataStoreLive.js +2 -1
- package/layers/EnvironmentDetectorLive.js +3 -6
- package/layers/LoggerLive.js +6 -14
- package/layers/ProjectDiscoveryLive.js +25 -23
- package/layers/ProjectIdentityLive.js +36 -36
- package/layers/RunContextLive.js +33 -47
- package/package.json +20 -18
- package/platform.js +9 -3
- package/programs/hook-paths.js +29 -20
- package/programs/platform-sidecar.js +28 -12
- package/programs/record-turn.js +2 -1
- package/programs/resolve-session-for-recording.js +4 -2
- package/programs/session-env.js +7 -33
- package/services/ProjectIdentity.js +34 -17
- package/stores.js +27 -18
- package/tsdoc-metadata.json +1 -1
- package/utils/ensure-migrated.js +33 -37
- package/utils/format-fatal-error.js +55 -0
- package/utils/host-metadata.js +101 -0
- package/utils/package-manifest.js +21 -0
- package/utils/resolve-data-path.js +21 -4
- package/utils/resolve-project-key-from-cwd.js +31 -45
- package/utils/resolve-workspace-key.js +8 -4
- package/version.js +1 -1
package/index.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { AppDirs, AppDirs as AppDirs$1, AppDirsError, AppDirsError as AppDirsError$1, XdgEnvError } from "@effected/xdg";
|
|
1
|
+
import { AppDirs, AppDirs as AppDirs$1, AppDirsError, AppDirsError as AppDirsError$1, AppDirsSegmentError, XdgEnvError } from "@effected/xdg";
|
|
2
2
|
import { Context, Effect, FileSystem, Layer, LogLevel, Option, Path, Schema } from "effect";
|
|
3
3
|
import { ConfigFileShape } from "@effected/config-file";
|
|
4
|
-
import { Agent, AgentNotFoundError, AgentReport, ArtifactKind, ArtifactKind as ArtifactKind$1, ArtifactSuite, ArtifactSuite as ArtifactSuite$1, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorStatus, CacheManifest, CitedArtifact, CoverageBaselines, CoverageReport, DataStoreError, DetailLevel, DiscoveryError, Environment, Executor, FileCoverageReport, GoalDetail, GoalNotFoundError, GoalRow, GoalStatus, HistoryRecord, IdempotencyHit, IllegalStatusTransitionError, Phase, ProjectIdentityError, ProjectIdentityNotResolvableError, RegistrationConflictError, ResolvedThresholds, RunRetentionPolicy, StrayOutput, TddTaskAlreadyEndedError, TddTaskNotFoundError, TestClassification, TestRun, TrendEntry, TrendRecord, TurnPayload, VitestAgentConfig } from "@vitest-agent/sdk";
|
|
4
|
+
import { Agent, AgentNotFoundError, AgentReport, ArtifactKind, ArtifactKind as ArtifactKind$1, ArtifactSuite, ArtifactSuite as ArtifactSuite$1, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorStatus, CacheManifest, CitedArtifact, CoverageBaselines, CoverageReport, DataStoreError, DetailLevel, DiscoveryError, Environment, Executor, FileCoverageReport, GoalDetail, GoalNotFoundError, GoalRow, GoalStatus, HistoryRecord, IdempotencyHit, IllegalStatusTransitionError, Phase, ProjectIdentityError, ProjectIdentityNotResolvableError, ProjectIdentitySourceError, RegistrationConflictError, ResolvedThresholds, RunRetentionPolicy, StrayOutput, TddTaskAlreadyEndedError, TddTaskNotFoundError, TestClassification, TestRun, TrendEntry, TrendRecord, TurnPayload, VitestAgentConfig } from "@vitest-agent/sdk";
|
|
5
5
|
import { SqlClient } from "effect/sql/SqlClient";
|
|
6
6
|
import { CiName, RuntimeEnv } from "@effected/env";
|
|
7
7
|
import { WorkspaceDiscovery, WorkspaceDiscoveryFailure, WorkspaceRootNotFoundError } from "@effected/workspaces";
|
|
8
8
|
import { ChildProcessSpawner } from "effect/process";
|
|
9
9
|
import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
10
10
|
import { SqliteClient } from "@effect/sql-sqlite-node/SqliteClient";
|
|
11
|
-
import { StoreError, StoreMigrationError } from "@effected/store";
|
|
11
|
+
import { StoreError, StoreMigrationError, StoreShape } from "@effected/store";
|
|
12
12
|
import { SqlError } from "effect/sql/SqlError";
|
|
13
13
|
import { PlatformError } from "effect/PlatformError";
|
|
14
14
|
/**
|
|
@@ -35,12 +35,19 @@ export declare class VitestAgentConfigFile extends VitestAgentConfigFile_base {}
|
|
|
35
35
|
*
|
|
36
36
|
* 1. The workspace root (when `projectDir` is inside a pnpm/npm/yarn workspace).
|
|
37
37
|
* 2. The git repository root (when `projectDir` is inside a git repo).
|
|
38
|
-
* 3. Walking upward from `projectDir
|
|
38
|
+
* 3. Walking upward from `projectDir`, stopping at the git repository root
|
|
39
|
+
* (inclusive, compared physically) so a config file above the repository
|
|
40
|
+
* is never picked up. Outside a repository the walk runs to the
|
|
41
|
+
* filesystem root.
|
|
39
42
|
*
|
|
40
43
|
* The first found file wins (`MergeStrategy.firstMatch()`). If no file is
|
|
41
44
|
* present, downstream callers use `loadOrDefault(new VitestAgentConfig({}))`
|
|
42
45
|
* to get an empty config.
|
|
43
46
|
*
|
|
47
|
+
* The repository root comes from `git rev-parse --show-toplevel` through
|
|
48
|
+
* `@effected/git`, using a `Git` or `ChildProcessSpawner` service if the
|
|
49
|
+
* context has one; neither is required (see `repoRootCeiling`).
|
|
50
|
+
*
|
|
44
51
|
* @param projectDir - Absolute path inside the user's workspace. Resolvers
|
|
45
52
|
* anchor here rather than `process.cwd()` so the plugin-spawned MCP server
|
|
46
53
|
* sees the right config even when invoked from elsewhere.
|
|
@@ -1386,6 +1393,10 @@ export declare const LoggerLive: (level?: LogLevel.LogLevel, logFile?: string, e
|
|
|
1386
1393
|
* Resolve log level from option or environment variable.
|
|
1387
1394
|
* Priority: explicit option \> `VITEST_REPORTER_LOG_LEVEL` in `env` \> undefined.
|
|
1388
1395
|
*
|
|
1396
|
+
* Case-insensitive; `warning` is accepted for `Warn`. An unset or empty value,
|
|
1397
|
+
* or one that names no level, resolves to `undefined` (no diagnostics) without
|
|
1398
|
+
* a warning.
|
|
1399
|
+
*
|
|
1389
1400
|
* @param env - the environment map to consult (the front end passes `process.env`)
|
|
1390
1401
|
* @param option - explicit override
|
|
1391
1402
|
* @public
|
|
@@ -1632,12 +1643,13 @@ declare const ProjectIdentity_base: Context.ServiceClass<ProjectIdentity, "vites
|
|
|
1632
1643
|
*/
|
|
1633
1644
|
export declare class ProjectIdentity extends ProjectIdentity_base {}
|
|
1634
1645
|
/**
|
|
1635
|
-
* Effect environment type required by `
|
|
1636
|
-
*
|
|
1646
|
+
* Effect environment type required by the `ProjectIdentityLive` layer.
|
|
1647
|
+
* Exported only so the layer's
|
|
1637
1648
|
* `Layer.Layer<ProjectIdentity, never, CandidateContext>` signature
|
|
1638
1649
|
* has a named referent — consumers should provide the four named
|
|
1639
1650
|
* services (FileSystem, WorkspaceDiscovery, VitestAgentConfigFile,
|
|
1640
|
-
*
|
|
1651
|
+
* ChildProcessSpawner, which feeds the layer's own `@effected/git` `Git`)
|
|
1652
|
+
* rather than reference this type directly.
|
|
1641
1653
|
*
|
|
1642
1654
|
* @public
|
|
1643
1655
|
*/
|
|
@@ -1719,9 +1731,10 @@ export declare const RunContextService: typeof RunContext$;
|
|
|
1719
1731
|
export type RunContextService = RunContext$;
|
|
1720
1732
|
/**
|
|
1721
1733
|
* Live layer. Requires `ChildProcessSpawner` (provided by
|
|
1722
|
-
* `NodeServices.layer` at the entry point)
|
|
1723
|
-
*
|
|
1724
|
-
*
|
|
1734
|
+
* `NodeServices.layer` at the entry point), which feeds `@effected/git`'s
|
|
1735
|
+
* `Git` for branch / sha / dirty / upstream / toplevel. Every git failure is
|
|
1736
|
+
* recorded as `null`. Host metadata comes from `probeHostMetadataFromEnv`
|
|
1737
|
+
* over the injected `env` map, captured once when the layer is built.
|
|
1725
1738
|
*
|
|
1726
1739
|
* @param env - the environment map to probe (the front end passes `process.env`)
|
|
1727
1740
|
* @public
|
|
@@ -1820,6 +1833,18 @@ declare const migration$3: Effect.Effect<void, import("effect/sql/SqlError").Sql
|
|
|
1820
1833
|
* @public
|
|
1821
1834
|
*/
|
|
1822
1835
|
type MigrationRecord = Record<string, Effect.Effect<void, SqlError, SqlClient>>;
|
|
1836
|
+
declare const RegistryStore_base: Context.ServiceClass<RegistryStore, "vitest-agent/RegistryStore", StoreShape>;
|
|
1837
|
+
/**
|
|
1838
|
+
* The global discovery registry (`<XDG data>/vitest-agent/registry.db`).
|
|
1839
|
+
* @internal
|
|
1840
|
+
*/
|
|
1841
|
+
export declare class RegistryStore extends RegistryStore_base {}
|
|
1842
|
+
declare const SessionMapStore_base: Context.ServiceClass<SessionMapStore, "vitest-agent/SessionMapStore", StoreShape>;
|
|
1843
|
+
/**
|
|
1844
|
+
* The per-client session map (`sessions.db`, under the host's plugin data dir).
|
|
1845
|
+
* @internal
|
|
1846
|
+
*/
|
|
1847
|
+
export declare class SessionMapStore extends SessionMapStore_base {}
|
|
1823
1848
|
/**
|
|
1824
1849
|
* A SQLite connection plus the migrator that brings it to the head of a
|
|
1825
1850
|
* migration set. Returned by {@link makeSqliteStack}.
|
|
@@ -2001,7 +2026,9 @@ export type HookPathsError = XdgEnvError | AppDirsError$1 | PlatformError | Proj
|
|
|
2001
2026
|
*
|
|
2002
2027
|
* Precedence: the `CLAUDE_PLUGIN_DATA` env var, then
|
|
2003
2028
|
* `VITEST_AGENT_SESSION_MAP_DIR`, then `~/.vitest-agent/` (`HOME`, then
|
|
2004
|
-
* `USERPROFILE`). The
|
|
2029
|
+
* `USERPROFILE`). The two variables are read through `@effected/engine`'s
|
|
2030
|
+
* `LaunchContext.resolve`: a value is trimmed, and a blank or unsubstituted
|
|
2031
|
+
* `${...}` placeholder value counts as unset. The chosen directory is created. Fails with
|
|
2005
2032
|
* `ProjectIdentityNotResolvableError` when no home directory is resolvable.
|
|
2006
2033
|
*
|
|
2007
2034
|
* @param env - the environment map to consult (the front end passes `process.env`)
|
|
@@ -2038,6 +2065,20 @@ export interface SidecarPaths {
|
|
|
2038
2065
|
/** Absolute path to the global `registry.db`. */
|
|
2039
2066
|
readonly registryDbPath: string;
|
|
2040
2067
|
}
|
|
2068
|
+
/**
|
|
2069
|
+
* The per-client session map store (`sessions.db`) at `filename`, migrated,
|
|
2070
|
+
* with the shared per-connection settings applied on open.
|
|
2071
|
+
*
|
|
2072
|
+
* @internal
|
|
2073
|
+
*/
|
|
2074
|
+
export declare const SessionMapStoreLive: (filename: string) => Layer.Layer<SessionMapStore, import("@effected/store").StoreError | import("@effected/store").StoreMigrationError, never>;
|
|
2075
|
+
/**
|
|
2076
|
+
* The global discovery registry store (`registry.db`) at `filename`, migrated,
|
|
2077
|
+
* with the shared per-connection settings applied on open.
|
|
2078
|
+
*
|
|
2079
|
+
* @internal
|
|
2080
|
+
*/
|
|
2081
|
+
export declare const RegistryStoreLive: (filename: string) => Layer.Layer<RegistryStore, import("@effected/store").StoreError | import("@effected/store").StoreMigrationError, never>;
|
|
2041
2082
|
/**
|
|
2042
2083
|
* Build the sidecar Live layer for the supplied SQLite paths.
|
|
2043
2084
|
*
|
|
@@ -2463,15 +2504,6 @@ export interface SessionContext {
|
|
|
2463
2504
|
readonly conversationId: string;
|
|
2464
2505
|
readonly mainAgentId: string;
|
|
2465
2506
|
}
|
|
2466
|
-
/**
|
|
2467
|
-
* Parse `export KEY=value` lines from a session-env hook file into a
|
|
2468
|
-
* plain record. Non-export lines are ignored.
|
|
2469
|
-
*
|
|
2470
|
-
* @param content - the raw text of a session-env hook file
|
|
2471
|
-
* @returns a record of export names to unquoted values
|
|
2472
|
-
* @public
|
|
2473
|
-
*/
|
|
2474
|
-
export declare const parseSessionEnvExports: (content: string) => Record<string, string>;
|
|
2475
2507
|
/**
|
|
2476
2508
|
* The synchronous filesystem reads {@link recoverSessionContextFromSessionEnv}
|
|
2477
2509
|
* performs. Each member throws on a missing or unreadable path, exactly as
|
|
@@ -2482,8 +2514,12 @@ export declare const parseSessionEnvExports: (content: string) => Record<string,
|
|
|
2482
2514
|
export interface SessionEnvFileSystem {
|
|
2483
2515
|
/** Entry names directly under `path` (`readdirSync`). */
|
|
2484
2516
|
readonly readDirectory: (path: string) => ReadonlyArray<string>;
|
|
2485
|
-
/**
|
|
2486
|
-
|
|
2517
|
+
/**
|
|
2518
|
+
* The raw bytes of the file at `path` (`readFileSync(path)`). Bytes, not
|
|
2519
|
+
* text: bash 3.2's `%q` mixes raw UTF-8 bytes with octal escapes inside
|
|
2520
|
+
* one `$'…'`, which only a byte-level parse reads back as bash does.
|
|
2521
|
+
*/
|
|
2522
|
+
readonly readFile: (path: string) => Uint8Array;
|
|
2487
2523
|
/** Modification time of the file at `path`, in epoch milliseconds (`statSync(path).mtimeMs`). */
|
|
2488
2524
|
readonly mtimeMs: (path: string) => number;
|
|
2489
2525
|
}
|
|
@@ -2616,10 +2652,21 @@ export declare function assembleManifest(rows: ManifestRow[], dbPath: string): C
|
|
|
2616
2652
|
*/
|
|
2617
2653
|
export declare function assembleHistoryRecord(rows: HistoryRow[]): AssembledHistoryRecord;
|
|
2618
2654
|
/**
|
|
2619
|
-
* Ensure the SQLite database at `dbPath` is migrated
|
|
2620
|
-
*
|
|
2621
|
-
*
|
|
2622
|
-
*
|
|
2655
|
+
* Ensure the SQLite database at `dbPath` is migrated to the head of the
|
|
2656
|
+
* project migration set.
|
|
2657
|
+
*
|
|
2658
|
+
* Runs through `@effected/store`'s `Store.migrateOnce`: at most one
|
|
2659
|
+
* successful migration run per database per process, shared by every
|
|
2660
|
+
* concurrent caller (and by every copy of the kit loaded in the process).
|
|
2661
|
+
* The run is keyed on the real path of the file's directory, so a relative,
|
|
2662
|
+
* an absolute and a symlinked spelling of one database share it. A failed
|
|
2663
|
+
* run (for example `SQLITE_BUSY`) is not remembered: the next call tries
|
|
2664
|
+
* again. The connection opens with the same per-connection settings as
|
|
2665
|
+
* `makeSqliteStack` (`busy_timeout`, `synchronous = NORMAL`,
|
|
2666
|
+
* `foreign_keys = ON`).
|
|
2667
|
+
*
|
|
2668
|
+
* The parent directory must exist; `dbPath` must name a file (`":memory:"`
|
|
2669
|
+
* is a wiring defect).
|
|
2623
2670
|
*
|
|
2624
2671
|
* @param dbPath - the SQLite file to migrate
|
|
2625
2672
|
* @param logLevel - the diagnostics level for the migrator's records; see `LoggerLive`
|
|
@@ -2629,12 +2676,6 @@ export declare function assembleHistoryRecord(rows: HistoryRow[]): AssembledHist
|
|
|
2629
2676
|
* @public
|
|
2630
2677
|
*/
|
|
2631
2678
|
export declare function ensureMigrated(dbPath: string, logLevel?: LogLevel.LogLevel, logFile?: string, env?: Readonly<Record<string, string | undefined>>): Promise<void>;
|
|
2632
|
-
/**
|
|
2633
|
-
* Reset the migration cache. Test-only.
|
|
2634
|
-
*
|
|
2635
|
-
* @internal
|
|
2636
|
-
*/
|
|
2637
|
-
export declare function _resetMigrationCacheForTesting(): void;
|
|
2638
2679
|
/** @public */
|
|
2639
2680
|
export interface FailureSignatureInput {
|
|
2640
2681
|
readonly error_name: string;
|
|
@@ -2647,6 +2688,64 @@ export interface FailureSignatureInput {
|
|
|
2647
2688
|
export declare const normalizeAssertionShape: (message: string) => string | null;
|
|
2648
2689
|
/** @public */
|
|
2649
2690
|
export declare const computeFailureSignature: (input: FailureSignatureInput) => string;
|
|
2691
|
+
/**
|
|
2692
|
+
* Format an unexpected failure for stderr, with the issue URL appended.
|
|
2693
|
+
*
|
|
2694
|
+
* @remarks
|
|
2695
|
+
* Under Effect v4, `Effect.runPromise` / `Effect.runSync` reject with
|
|
2696
|
+
* `Cause.squash` of the failure: the error value itself, with no
|
|
2697
|
+
* FiberFailure wrapper and no `Cause` attached. So a `Cause` is used as it
|
|
2698
|
+
* is (one from `Effect.runPromiseExit` keeps every reason and the span
|
|
2699
|
+
* trail), and anything else is rendered as a defect, `Cause.die(err)`: an
|
|
2700
|
+
* unexpected throw is a bug to report, and a defect is the shape whose
|
|
2701
|
+
* document carries the stack. `@effected/cli`'s `CliFailure.toDoc` keeps
|
|
2702
|
+
* the program's own frames, hides `node_modules` and runtime frames,
|
|
2703
|
+
* follows an `Error.cause` chain, and renders a non-`Error` value as its
|
|
2704
|
+
* text.
|
|
2705
|
+
*
|
|
2706
|
+
* Total: `CliFailure.toDoc` guards the `stack`, `message`, `cause` and
|
|
2707
|
+
* `issue` reads itself, but wrapping a `Proxy` whose traps throw can still
|
|
2708
|
+
* throw, so any throw while formatting falls back to a fixed marker naming
|
|
2709
|
+
* the value's constructor.
|
|
2710
|
+
*
|
|
2711
|
+
* @param err - the thrown value, rejection reason, or `Cause`
|
|
2712
|
+
* @returns the formatted failure followed by the issue-report line
|
|
2713
|
+
* @public
|
|
2714
|
+
*/
|
|
2715
|
+
export declare const formatFatalError: (err: unknown) => string;
|
|
2716
|
+
/**
|
|
2717
|
+
* The host identity a test run records: `host_source` / `host_value` /
|
|
2718
|
+
* `host_metadata` on `test_runs`.
|
|
2719
|
+
*
|
|
2720
|
+
* @public
|
|
2721
|
+
*/
|
|
2722
|
+
export interface HostMetadataResult {
|
|
2723
|
+
readonly source: string | null;
|
|
2724
|
+
readonly value: string | null;
|
|
2725
|
+
readonly metadata: Record<string, unknown> | null;
|
|
2726
|
+
}
|
|
2727
|
+
/**
|
|
2728
|
+
* The host identity of an environment record, from `@effected/env`'s
|
|
2729
|
+
* `RuntimeEnv` snapshot (`session`, `ci` / `ciRun`, `termProgram`).
|
|
2730
|
+
*
|
|
2731
|
+
* Priority: the terminal session (`TMUX_PANE`, `WT_SESSION`, `WEZTERM_PANE`,
|
|
2732
|
+
* `KITTY_WINDOW_ID`, `TERM_SESSION_ID`), then the CI job (GitHub Actions run,
|
|
2733
|
+
* Buildkite, CircleCI, GitLab job). The first match is the source; when
|
|
2734
|
+
* nothing matches, all three fields are `null`. Every metadata object
|
|
2735
|
+
* carries `term_program` / `term_program_version` when they are set, then
|
|
2736
|
+
* the source's own keys (`tmux_socket` for a tmux pane, `ci`, `ci_provider`
|
|
2737
|
+
* and `github_run_attempt` for CI), in that key order.
|
|
2738
|
+
*
|
|
2739
|
+
* GNU screen (`STY`) is not a source: the snapshot is taken with `STY`
|
|
2740
|
+
* removed, so a screen session falls through to the next source exactly as
|
|
2741
|
+
* it did before the kit, and no `STY` row is ever written. The
|
|
2742
|
+
* `VSCODE_INJECTION` + parent-pid sources need `process.ppid` and are the
|
|
2743
|
+
* caller's.
|
|
2744
|
+
*
|
|
2745
|
+
* @param env - the environment record to read (the front end passes `process.env`)
|
|
2746
|
+
* @public
|
|
2747
|
+
*/
|
|
2748
|
+
export declare const probeHostMetadataFromEnv: (env: Readonly<Record<string, string | undefined>>) => HostMetadataResult;
|
|
2650
2749
|
/**
|
|
2651
2750
|
* Run `effect` as a nested transaction that keeps the
|
|
2652
2751
|
* {@link withPersistTransaction} caches honest.
|
|
@@ -2733,7 +2832,10 @@ export interface ResolveDataPathOptions {
|
|
|
2733
2832
|
*
|
|
2734
2833
|
* 1. `options.cacheDir` — programmatic override.
|
|
2735
2834
|
* 2. `cacheDir` from `vitest-agent.config.toml`.
|
|
2736
|
-
* 3. `<XDG data>/<normalized projectKey from config>/data.db`.
|
|
2835
|
+
* 3. `<XDG data>/<normalized projectKey from config>/data.db`. A
|
|
2836
|
+
* `projectKey` that normalizes to `.` or `..` fails with
|
|
2837
|
+
* `ProjectIdentitySourceError` (source `toml`) rather than writing
|
|
2838
|
+
* outside the data root.
|
|
2737
2839
|
* 4. `<XDG data>/<projectKey resolved from cwd's package.json>/data.db`.
|
|
2738
2840
|
* Source 4 prefers `repository.url` (canonicalized to `host__path`),
|
|
2739
2841
|
* falling back to the normalized `name`. This matches the sidecar
|
|
@@ -2752,7 +2854,7 @@ export interface ResolveDataPathOptions {
|
|
|
2752
2854
|
* @param options - Optional programmatic overrides.
|
|
2753
2855
|
* @public
|
|
2754
2856
|
*/
|
|
2755
|
-
export declare const resolveDataPath: (projectDir: string, options?: ResolveDataPathOptions) => Effect.Effect<string, import("@effected/xdg").AppDirsError | import("effect/PlatformError").PlatformError | import("@effected/config-file").ConfigReadError<import("@effected/config-file").ConfigCodecError>, AppDirs$1 | FileSystem.FileSystem | VitestAgentConfigFile>;
|
|
2857
|
+
export declare const resolveDataPath: (projectDir: string, options?: ResolveDataPathOptions) => Effect.Effect<string, import("@effected/xdg").AppDirsError | import("effect/PlatformError").PlatformError | ProjectIdentitySourceError | import("@effected/config-file").ConfigReadError<import("@effected/config-file").ConfigCodecError>, AppDirs$1 | FileSystem.FileSystem | VitestAgentConfigFile>;
|
|
2756
2858
|
/**
|
|
2757
2859
|
* Compute the project key for a workspace by reading `package.json`
|
|
2758
2860
|
* fields directly. Returns the canonical `host__path` form when a git
|
|
@@ -2771,7 +2873,8 @@ export declare const resolveProjectKeyFromCwd: (cwd: string) => string;
|
|
|
2771
2873
|
/**
|
|
2772
2874
|
* {@link resolveProjectKeyFromCwd} read through the ambient
|
|
2773
2875
|
* `FileSystem` service instead of `node:fs` — same upward walk to the
|
|
2774
|
-
* nearest `package.json
|
|
2876
|
+
* nearest `package.json` (`@effected/walker`'s `Walker.ascend` +
|
|
2877
|
+
* `Walker.findUpward`), same precedence, same basename fallback.
|
|
2775
2878
|
*
|
|
2776
2879
|
* Never fails: a filesystem error while probing or reading is treated like
|
|
2777
2880
|
* a missing or malformed `package.json`, exactly as the synchronous form
|
|
@@ -2786,7 +2889,8 @@ export declare const resolveProjectKeyFromCwdEffect: (cwd: string) => Effect.Eff
|
|
|
2786
2889
|
* `projectDir`.
|
|
2787
2890
|
*
|
|
2788
2891
|
* The key is the root `package.json`'s `name`, normalized via
|
|
2789
|
-
* `
|
|
2892
|
+
* `@effected/xdg`'s `AppDirs.segment` (a name that maps to `.` or `..` fails
|
|
2893
|
+
* with `AppDirsSegmentError`). This becomes the directory segment under
|
|
2790
2894
|
* `$XDG_DATA_HOME/vitest-agent/` where the SQLite database lives.
|
|
2791
2895
|
*
|
|
2792
2896
|
* Fails with `WorkspaceRootNotFoundError` when no root workspace is
|
|
@@ -2806,7 +2910,7 @@ export declare const resolveProjectKeyFromCwdEffect: (cwd: string) => Effect.Eff
|
|
|
2806
2910
|
* reporter's resolved `projectDir` (CLAUDE_PROJECT_DIR or process.cwd()).
|
|
2807
2911
|
* @public
|
|
2808
2912
|
*/
|
|
2809
|
-
export declare const resolveWorkspaceKey: (projectDir: string) => Effect.Effect<string, WorkspaceRootNotFoundError | WorkspaceDiscoveryFailure, WorkspaceDiscovery>;
|
|
2913
|
+
export declare const resolveWorkspaceKey: (projectDir: string) => Effect.Effect<string, WorkspaceRootNotFoundError | WorkspaceDiscoveryFailure | AppDirsSegmentError, WorkspaceDiscovery>;
|
|
2810
2914
|
/** Build-time injected package version (see architecture.md D36). @public */
|
|
2811
2915
|
export declare const CURRENT_ENGINE_VERSION: string;
|
|
2812
2916
|
export { type AppDirs, type AppDirsError, type ArtifactKind, type ArtifactSuite, type FormatTriageOptions, type FormatWrapupOptions, type MigrationRecord, type Phase, type WrapupKind, migration as migration0001, migration$1 as migration0002, migration$2 as registryMigration0001, migration$3 as sessionMapMigration0001 };
|
package/index.js
CHANGED
|
@@ -28,6 +28,7 @@ import { ProjectDiscoveryTest } from "./layers/ProjectDiscoveryTest.js";
|
|
|
28
28
|
import { ProjectIdentity, resolveProjectIdentityFromCandidates } from "./services/ProjectIdentity.js";
|
|
29
29
|
import { ProjectIdentityLive, ProjectIdentityTest } from "./layers/ProjectIdentityLive.js";
|
|
30
30
|
import { AgentContext, RunContext, RunContext$, RunContextService } from "./services/RunContext.js";
|
|
31
|
+
import { probeHostMetadataFromEnv } from "./utils/host-metadata.js";
|
|
31
32
|
import { RunContextLive, RunContextTest } from "./layers/RunContextLive.js";
|
|
32
33
|
import { formatStrayOutputLine, formatTriageEffect } from "./lib/format-triage.js";
|
|
33
34
|
import { formatWrapupEffect } from "./lib/format-wrapup.js";
|
|
@@ -36,10 +37,11 @@ import migration$1 from "./migrations/0002_test_artifacts.js";
|
|
|
36
37
|
import { PROJECT_MIGRATIONS } from "./migrations/index.js";
|
|
37
38
|
import migration$2 from "./migrations/registry_0001_initial.js";
|
|
38
39
|
import migration$3 from "./migrations/session_map_0001_initial.js";
|
|
40
|
+
import { RegistryStore, SessionMapStore } from "./stores.js";
|
|
39
41
|
import { NodePlatformLayer, PlatformLive, makeSqliteStack } from "./platform.js";
|
|
40
42
|
import { endAgentEffect } from "./programs/end-agent.js";
|
|
41
43
|
import { DATA_DB_FILENAME, REGISTRY_DB_FILENAME, REGISTRY_STORE_OPTIONS, SESSIONS_DB_FILENAME, resolveHookPaths, resolveSessionMapPath } from "./programs/hook-paths.js";
|
|
42
|
-
import { SidecarPlatformLive } from "./programs/platform-sidecar.js";
|
|
44
|
+
import { RegistryStoreLive, SessionMapStoreLive, SidecarPlatformLive } from "./programs/platform-sidecar.js";
|
|
43
45
|
import { recordSessionEnd, recordSessionStart } from "./programs/record-session.js";
|
|
44
46
|
import { resolveSessionForRecording } from "./programs/resolve-session-for-recording.js";
|
|
45
47
|
import { dispatchRecordTddArtifactEffect, recordTddArtifactByTaskIdEffect, recordTddArtifactEffect } from "./programs/record-tdd-artifact.js";
|
|
@@ -47,14 +49,15 @@ import { parseAndValidateTurnPayload, recordTurnEffect } from "./programs/record
|
|
|
47
49
|
import { recordRunWorkspaceChangesEffect } from "./programs/record-workspace-changes.js";
|
|
48
50
|
import { IDEMPOTENCY_ROOT_SENTINEL, deriveIdempotencyKey } from "./services/idempotency.js";
|
|
49
51
|
import { registerAgentEffect } from "./programs/register-agent.js";
|
|
50
|
-
import {
|
|
52
|
+
import { recoverSessionContextFromSessionEnv } from "./programs/session-env.js";
|
|
51
53
|
import { resolveProjectDir } from "./project-dir.js";
|
|
52
54
|
import { assembleHistoryRecord, assembleManifest } from "./sql/assemblers.js";
|
|
53
|
-
import {
|
|
55
|
+
import { ensureMigrated } from "./utils/ensure-migrated.js";
|
|
54
56
|
import { computeFailureSignature, normalizeAssertionShape } from "./utils/failure-signature.js";
|
|
57
|
+
import { formatFatalError } from "./utils/format-fatal-error.js";
|
|
55
58
|
import { resolveProjectKeyFromCwd, resolveProjectKeyFromCwdEffect } from "./utils/resolve-project-key-from-cwd.js";
|
|
56
59
|
import { DATABASE_FILENAME, resolveDataPath } from "./utils/resolve-data-path.js";
|
|
57
60
|
import { resolveWorkspaceKey } from "./utils/resolve-workspace-key.js";
|
|
58
61
|
import { CURRENT_ENGINE_VERSION } from "./version.js";
|
|
59
62
|
|
|
60
|
-
export { APP_NAMESPACE, AgentContext, CURRENT_ENGINE_VERSION, ConfigLive, DATABASE_FILENAME, DATA_DB_FILENAME, DATA_FALLBACK_DIR, DataReader, DataReaderLive, DataStore, DataStoreLive, DetailResolver, DetailResolverLive, DiscoveryRegistry, DiscoveryRegistryLive, EnvironmentDetector, EnvironmentDetectorLive, EnvironmentDetectorTest, ExecutorResolver, ExecutorResolverLive, HistoryTracker, HistoryTrackerLive, HistoryTrackerTest, IDEMPOTENCY_ROOT_SENTINEL, INLINE_ATTACHMENT_BODY_CAP_BYTES, LoggerLive, NodePlatformLayer, OutputPipelineLive, PROJECT_MIGRATIONS, PathResolutionLive, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PlatformLive, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityLive, ProjectIdentityTest, REGISTRY_DB_FILENAME, REGISTRY_STORE_OPTIONS, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, SESSIONS_DB_FILENAME, SidecarPlatformLive, VitestAgentConfigFile,
|
|
63
|
+
export { APP_NAMESPACE, AgentContext, CURRENT_ENGINE_VERSION, ConfigLive, DATABASE_FILENAME, DATA_DB_FILENAME, DATA_FALLBACK_DIR, DataReader, DataReaderLive, DataStore, DataStoreLive, DetailResolver, DetailResolverLive, DiscoveryRegistry, DiscoveryRegistryLive, EnvironmentDetector, EnvironmentDetectorLive, EnvironmentDetectorTest, ExecutorResolver, ExecutorResolverLive, HistoryTracker, HistoryTrackerLive, HistoryTrackerTest, IDEMPOTENCY_ROOT_SENTINEL, INLINE_ATTACHMENT_BODY_CAP_BYTES, LoggerLive, NodePlatformLayer, OutputPipelineLive, PROJECT_MIGRATIONS, PathResolutionLive, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PlatformLive, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityLive, ProjectIdentityTest, REGISTRY_DB_FILENAME, REGISTRY_STORE_OPTIONS, RegistryStore, RegistryStoreLive, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, SESSIONS_DB_FILENAME, SessionMapStore, SessionMapStoreLive, SidecarPlatformLive, VitestAgentConfigFile, assembleHistoryRecord, assembleManifest, classifyEnvironment, computeFailureSignature, deriveIdempotencyKey, dispatchRecordTddArtifactEffect, endAgentEffect, ensureMigrated, formatFatalError, formatStrayOutputLine, formatTriageEffect, formatWrapupEffect, historyKey, makeSqliteStack, migration as migration0001, migration$1 as migration0002, normalizeAssertionShape, parseAndValidateTurnPayload, probeHostMetadataFromEnv, recordRunWorkspaceChangesEffect, recordSessionEnd, recordSessionStart, recordTddArtifactByTaskIdEffect, recordTddArtifactEffect, recordTurnEffect, recoverSessionContextFromSessionEnv, registerAgentEffect, migration$2 as registryMigration0001, resolveDataPath, resolveHookPaths, resolveLogFile, resolveLogLevel, resolveProjectDir, resolveProjectIdentityFromCandidates, resolveProjectKeyFromCwd, resolveProjectKeyFromCwdEffect, resolveSessionForRecording, resolveSessionMapPath, resolveWorkspaceKey, migration$3 as sessionMapMigration0001, withPersistSavepoint, withPersistTransaction };
|
package/layers/ConfigLive.js
CHANGED
|
@@ -1,29 +1,55 @@
|
|
|
1
1
|
import { VitestAgentConfigFile } from "../services/Config.js";
|
|
2
2
|
import { ConfigFile, ConfigResolver, MergeStrategy } from "@effected/config-file";
|
|
3
3
|
import { TomlCodec } from "@effected/config-file/toml";
|
|
4
|
+
import { Git } from "@effected/git";
|
|
4
5
|
import { VitestAgentConfig } from "@vitest-agent/sdk";
|
|
6
|
+
import { Effect, Layer, Option } from "effect";
|
|
7
|
+
import { ChildProcessSpawner } from "effect/process";
|
|
5
8
|
|
|
6
9
|
// src/layers/ConfigLive.ts
|
|
7
10
|
const CONFIG_FILENAME = "vitest-agent.config.toml";
|
|
8
11
|
/**
|
|
12
|
+
* The repository root git answers for `projectDir`, used as the upward
|
|
13
|
+
* walk's ceiling: the `Git` service when one is in context, else `Git.layer`
|
|
14
|
+
* over a `ChildProcessSpawner` when one is (every front end provides
|
|
15
|
+
* `NodeServices.layer`), else no answer. Neither is a requirement: outside
|
|
16
|
+
* a repository, with no `git` on `PATH`, or with no spawner at all, the walk
|
|
17
|
+
* runs to the filesystem root as it always did.
|
|
18
|
+
*/
|
|
19
|
+
const repoRootCeiling = (projectDir) => Effect.gen(function* () {
|
|
20
|
+
const ask = (git) => git.repoRoot(projectDir).pipe(Effect.orElseSucceed(() => void 0));
|
|
21
|
+
const git = yield* Effect.serviceOption(Git);
|
|
22
|
+
if (Option.isSome(git)) return yield* ask(git.value);
|
|
23
|
+
const spawner = yield* Effect.serviceOption(ChildProcessSpawner.ChildProcessSpawner);
|
|
24
|
+
if (Option.isNone(spawner)) return void 0;
|
|
25
|
+
return yield* Effect.flatMap(Git, ask).pipe(Effect.provide(Git.layer), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner.value));
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
9
28
|
* Build the live `ConfigFile` layer for a given project directory.
|
|
10
29
|
*
|
|
11
30
|
* Resolves the optional `vitest-agent.config.toml` from (in order):
|
|
12
31
|
*
|
|
13
32
|
* 1. The workspace root (when `projectDir` is inside a pnpm/npm/yarn workspace).
|
|
14
33
|
* 2. The git repository root (when `projectDir` is inside a git repo).
|
|
15
|
-
* 3. Walking upward from `projectDir
|
|
34
|
+
* 3. Walking upward from `projectDir`, stopping at the git repository root
|
|
35
|
+
* (inclusive, compared physically) so a config file above the repository
|
|
36
|
+
* is never picked up. Outside a repository the walk runs to the
|
|
37
|
+
* filesystem root.
|
|
16
38
|
*
|
|
17
39
|
* The first found file wins (`MergeStrategy.firstMatch()`). If no file is
|
|
18
40
|
* present, downstream callers use `loadOrDefault(new VitestAgentConfig({}))`
|
|
19
41
|
* to get an empty config.
|
|
20
42
|
*
|
|
43
|
+
* The repository root comes from `git rev-parse --show-toplevel` through
|
|
44
|
+
* `@effected/git`, using a `Git` or `ChildProcessSpawner` service if the
|
|
45
|
+
* context has one; neither is required (see `repoRootCeiling`).
|
|
46
|
+
*
|
|
21
47
|
* @param projectDir - Absolute path inside the user's workspace. Resolvers
|
|
22
48
|
* anchor here rather than `process.cwd()` so the plugin-spawned MCP server
|
|
23
49
|
* sees the right config even when invoked from elsewhere.
|
|
24
50
|
* @public
|
|
25
51
|
*/
|
|
26
|
-
const ConfigLive = (projectDir) => ConfigFile.layer(VitestAgentConfigFile, {
|
|
52
|
+
const ConfigLive = (projectDir) => Layer.unwrap(Effect.map(repoRootCeiling(projectDir), (ceiling) => ConfigFile.layer(VitestAgentConfigFile, {
|
|
27
53
|
schema: VitestAgentConfig,
|
|
28
54
|
codec: TomlCodec,
|
|
29
55
|
strategy: MergeStrategy.firstMatch(),
|
|
@@ -38,9 +64,10 @@ const ConfigLive = (projectDir) => ConfigFile.layer(VitestAgentConfigFile, {
|
|
|
38
64
|
}),
|
|
39
65
|
ConfigResolver.upwardWalk({
|
|
40
66
|
filename: CONFIG_FILENAME,
|
|
41
|
-
cwd: projectDir
|
|
67
|
+
cwd: projectDir,
|
|
68
|
+
...ceiling !== void 0 && { ceiling }
|
|
42
69
|
})
|
|
43
70
|
]
|
|
44
|
-
});
|
|
71
|
+
})));
|
|
45
72
|
|
|
46
73
|
export { ConfigLive };
|
package/layers/DataStoreLive.js
CHANGED
|
@@ -72,7 +72,8 @@ const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
|
|
|
72
72
|
const writeSettings = (hash, settings, envVars) => Effect.gen(function* () {
|
|
73
73
|
yield* Effect.logDebug("writeSettings").pipe(Effect.annotateLogs({ hash }));
|
|
74
74
|
yield* sql`INSERT OR IGNORE INTO settings (hash, vitest_version, pool, environment, test_timeout, hook_timeout, slow_test_threshold, max_concurrency, max_workers, isolate, bail, globals, file_parallelism, sequence_seed, coverage_provider) VALUES (${hash}, ${settings.vitestVersion}, ${settings.pool ?? null}, ${settings.environment ?? null}, ${settings.testTimeout ?? null}, ${settings.hookTimeout ?? null}, ${settings.slowTestThreshold ?? null}, ${settings.maxConcurrency ?? null}, ${settings.maxWorkers ?? null}, ${boolToInt(settings.isolate)}, ${settings.bail ?? null}, ${boolToInt(settings.globals)}, ${boolToInt(settings.fileParallelism)}, ${settings.sequenceSeed ?? null}, ${settings.coverageProvider ?? null})`;
|
|
75
|
-
|
|
75
|
+
yield* sql`DELETE FROM settings_env_vars WHERE settings_hash = ${hash}`;
|
|
76
|
+
for (const [key, value] of Object.entries(envVars)) yield* sql`INSERT INTO settings_env_vars (settings_hash, key, value) VALUES (${hash}, ${key}, ${value})`;
|
|
76
77
|
}).pipe(Effect.annotateLogs("service", "DataStore"), Effect.mapError((e) => new DataStoreError({
|
|
77
78
|
operation: "write",
|
|
78
79
|
table: "settings",
|
|
@@ -3,11 +3,8 @@ import { Effect, Layer, Option } from "effect";
|
|
|
3
3
|
import { Audience, CurrentRuntimeEnv } from "@effected/env";
|
|
4
4
|
|
|
5
5
|
// src/layers/EnvironmentDetectorLive.ts
|
|
6
|
-
/** The engine environment for
|
|
7
|
-
const
|
|
8
|
-
"github-actions": "ci-github",
|
|
9
|
-
generic: "ci-generic"
|
|
10
|
-
};
|
|
6
|
+
/** The engine environment for a CI `@effected/env` names: GitHub Actions gets its own, every other provider is generic. */
|
|
7
|
+
const ciEnvironment = (ci) => ci === "github-actions" ? "ci-github" : "ci-generic";
|
|
11
8
|
/**
|
|
12
9
|
* Pure classification behind {@link EnvironmentDetectorLive}: an agent shell
|
|
13
10
|
* wins, then GitHub Actions, then generic CI, else a terminal.
|
|
@@ -25,7 +22,7 @@ const classifyEnvironment = (runtime) => {
|
|
|
25
22
|
case "agent": return "agent-shell";
|
|
26
23
|
case "ci": return Option.match(runtime.ci, {
|
|
27
24
|
onNone: () => "ci-generic",
|
|
28
|
-
onSome:
|
|
25
|
+
onSome: ciEnvironment
|
|
29
26
|
});
|
|
30
27
|
case "human": return "terminal";
|
|
31
28
|
}
|
package/layers/LoggerLive.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Layer,
|
|
1
|
+
import { Layer, Logger, Option } from "effect";
|
|
2
2
|
import { CurrentRuntimeEnv } from "@effected/env";
|
|
3
3
|
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
|
|
4
4
|
import * as NodePath from "@effect/platform-node/NodePath";
|
|
@@ -44,21 +44,14 @@ const LoggerLive = (level, logFile, env) => {
|
|
|
44
44
|
}).pipe(Layer.provide(Layer.merge(NodeFileSystem.layer, NodePath.layer)));
|
|
45
45
|
return env === void 0 ? sink : sink.pipe(Layer.provide(CurrentRuntimeEnv.layerFrom(env)));
|
|
46
46
|
};
|
|
47
|
-
const LEVEL_ALIASES = {
|
|
48
|
-
warn: "Warn",
|
|
49
|
-
warning: "Warn",
|
|
50
|
-
error: "Error",
|
|
51
|
-
info: "Info",
|
|
52
|
-
debug: "Debug",
|
|
53
|
-
trace: "Trace",
|
|
54
|
-
fatal: "Fatal",
|
|
55
|
-
all: "All",
|
|
56
|
-
none: "None"
|
|
57
|
-
};
|
|
58
47
|
/**
|
|
59
48
|
* Resolve log level from option or environment variable.
|
|
60
49
|
* Priority: explicit option \> `VITEST_REPORTER_LOG_LEVEL` in `env` \> undefined.
|
|
61
50
|
*
|
|
51
|
+
* Case-insensitive; `warning` is accepted for `Warn`. An unset or empty value,
|
|
52
|
+
* or one that names no level, resolves to `undefined` (no diagnostics) without
|
|
53
|
+
* a warning.
|
|
54
|
+
*
|
|
62
55
|
* @param env - the environment map to consult (the front end passes `process.env`)
|
|
63
56
|
* @param option - explicit override
|
|
64
57
|
* @public
|
|
@@ -66,8 +59,7 @@ const LEVEL_ALIASES = {
|
|
|
66
59
|
function resolveLogLevel(env, option) {
|
|
67
60
|
const raw = option ?? env.VITEST_REPORTER_LOG_LEVEL;
|
|
68
61
|
if (!raw) return void 0;
|
|
69
|
-
|
|
70
|
-
return LogLevel.values.includes(normalized) ? normalized : void 0;
|
|
62
|
+
return Option.getOrUndefined(CliLog.parseLevel(raw));
|
|
71
63
|
}
|
|
72
64
|
/**
|
|
73
65
|
* Resolve log file from option or environment variable
|
|
@@ -1,36 +1,38 @@
|
|
|
1
1
|
import { ProjectDiscovery } from "../services/ProjectDiscovery.js";
|
|
2
2
|
import { DiscoveryError, NON_DISCOVERABLE_DIRS } from "@vitest-agent/sdk";
|
|
3
|
-
import { Effect, FileSystem, Layer } from "effect";
|
|
3
|
+
import { Effect, FileSystem, Layer, Path } from "effect";
|
|
4
|
+
import { GlobPattern, GlobPatternOptions } from "@effected/glob";
|
|
5
|
+
import { descend } from "@effected/walker";
|
|
4
6
|
|
|
5
7
|
// src/layers/ProjectDiscoveryLive.ts
|
|
6
|
-
const
|
|
7
|
-
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
8
|
+
const SKIP_DIRS = [
|
|
8
9
|
...NON_DISCOVERABLE_DIRS,
|
|
9
10
|
"coverage",
|
|
10
11
|
".turbo",
|
|
11
12
|
".vite"
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
13
|
+
];
|
|
14
|
+
const TEST_FILE_GLOB = "**/*.{test,spec}.{ts,tsx}";
|
|
15
|
+
const TEST_FILE_GLOB_OPTIONS = GlobPatternOptions.make({ dot: true });
|
|
16
16
|
function testFileToSource(testFile) {
|
|
17
17
|
return testFile.replace(/\.(test|spec)\.(ts|tsx)$/, ".$2");
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Every test file under `rootDir`, as `${rootDir}/<relative>` paths sorted by
|
|
21
|
+
* relative path. `@effected/walker`'s `descend` follows directory symlinks
|
|
22
|
+
* (as the former `fs.stat`-based walk did) under its ancestor-chain cycle
|
|
23
|
+
* guard, so a link back to an ancestor is skipped instead of recursing until
|
|
24
|
+
* the OS answers `ELOOP` and failing the whole discovery.
|
|
25
|
+
*/
|
|
26
|
+
const walkTestFiles = (rootDir) => Effect.gen(function* () {
|
|
27
|
+
const pattern = yield* GlobPattern.compile(TEST_FILE_GLOB, TEST_FILE_GLOB_OPTIONS);
|
|
28
|
+
const relative = yield* descend(pattern, {
|
|
29
|
+
cwd: rootDir,
|
|
30
|
+
prune: SKIP_DIRS,
|
|
31
|
+
followSymlinks: true
|
|
32
32
|
});
|
|
33
|
-
|
|
33
|
+
const base = rootDir.endsWith("/") ? rootDir.slice(0, -1) : rootDir;
|
|
34
|
+
return relative.map((rel) => `${base}/${rel}`);
|
|
35
|
+
}).pipe(Effect.provide(Path.layer));
|
|
34
36
|
/** @public */
|
|
35
37
|
const ProjectDiscoveryLive = Layer.effect(ProjectDiscovery, Effect.gen(function* () {
|
|
36
38
|
const fs = yield* FileSystem.FileSystem;
|
|
@@ -44,11 +46,11 @@ const ProjectDiscoveryLive = Layer.effect(ProjectDiscovery, Effect.gen(function*
|
|
|
44
46
|
})));
|
|
45
47
|
return {
|
|
46
48
|
discoverTestFiles: (rootDir) => Effect.gen(function* () {
|
|
47
|
-
const testFiles =
|
|
49
|
+
const testFiles = yield* walkTestFiles(rootDir).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.mapError((error) => new DiscoveryError({
|
|
48
50
|
operation: "glob",
|
|
49
51
|
path: rootDir,
|
|
50
52
|
reason: String(error)
|
|
51
|
-
})))
|
|
53
|
+
})));
|
|
52
54
|
const entries = [];
|
|
53
55
|
for (const testFile of testFiles) {
|
|
54
56
|
const sourceFiles = yield* mapTestToSource(testFile);
|