@aria-cli/types 1.0.9 → 1.0.10

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.
Files changed (79) hide show
  1. package/package.json +4 -1
  2. package/dist-cjs/.tsbuildinfo +0 -1
  3. package/dist-cjs/errors.d.ts +0 -29
  4. package/dist-cjs/errors.js +0 -51
  5. package/dist-cjs/errors.js.map +0 -1
  6. package/dist-cjs/index.d.ts +0 -11
  7. package/dist-cjs/index.js +0 -29
  8. package/dist-cjs/index.js.map +0 -1
  9. package/dist-cjs/logger.d.ts +0 -54
  10. package/dist-cjs/logger.js +0 -166
  11. package/dist-cjs/logger.js.map +0 -1
  12. package/dist-cjs/memoria.d.ts +0 -428
  13. package/dist-cjs/memoria.js +0 -11
  14. package/dist-cjs/memoria.js.map +0 -1
  15. package/dist-cjs/models.d.ts +0 -322
  16. package/dist-cjs/models.js +0 -21
  17. package/dist-cjs/models.js.map +0 -1
  18. package/dist-cjs/native-tools.d.ts +0 -57
  19. package/dist-cjs/native-tools.js +0 -33
  20. package/dist-cjs/native-tools.js.map +0 -1
  21. package/dist-cjs/package.json +0 -3
  22. package/dist-cjs/relaunch.d.ts +0 -22
  23. package/dist-cjs/relaunch.js +0 -67
  24. package/dist-cjs/relaunch.js.map +0 -1
  25. package/dist-cjs/src/errors.d.ts +0 -29
  26. package/dist-cjs/src/errors.js +0 -51
  27. package/dist-cjs/src/errors.js.map +0 -1
  28. package/dist-cjs/src/index.d.ts +0 -10
  29. package/dist-cjs/src/index.js +0 -28
  30. package/dist-cjs/src/index.js.map +0 -1
  31. package/dist-cjs/src/logger.d.ts +0 -22
  32. package/dist-cjs/src/logger.js +0 -85
  33. package/dist-cjs/src/logger.js.map +0 -1
  34. package/dist-cjs/src/memoria.d.ts +0 -418
  35. package/dist-cjs/src/memoria.js +0 -11
  36. package/dist-cjs/src/memoria.js.map +0 -1
  37. package/dist-cjs/src/models.d.ts +0 -322
  38. package/dist-cjs/src/models.js +0 -21
  39. package/dist-cjs/src/models.js.map +0 -1
  40. package/dist-cjs/src/native-tools.d.ts +0 -57
  41. package/dist-cjs/src/native-tools.js +0 -33
  42. package/dist-cjs/src/native-tools.js.map +0 -1
  43. package/dist-cjs/src/relaunch.d.ts +0 -22
  44. package/dist-cjs/src/relaunch.js +0 -61
  45. package/dist-cjs/src/relaunch.js.map +0 -1
  46. package/dist-cjs/src/tool-outputs.d.ts +0 -37
  47. package/dist-cjs/src/tool-outputs.js +0 -5
  48. package/dist-cjs/src/tool-outputs.js.map +0 -1
  49. package/dist-cjs/stall-phase.d.ts +0 -2
  50. package/dist-cjs/stall-phase.js +0 -18
  51. package/dist-cjs/stall-phase.js.map +0 -1
  52. package/dist-cjs/tests/logger.test.d.ts +0 -1
  53. package/dist-cjs/tests/logger.test.js +0 -65
  54. package/dist-cjs/tests/logger.test.js.map +0 -1
  55. package/dist-cjs/tool-outputs.d.ts +0 -37
  56. package/dist-cjs/tool-outputs.js +0 -5
  57. package/dist-cjs/tool-outputs.js.map +0 -1
  58. package/dist-cjs/vitest.config.d.ts +0 -2
  59. package/dist-cjs/vitest.config.js +0 -19
  60. package/dist-cjs/vitest.config.js.map +0 -1
  61. package/src/errors.ts +0 -50
  62. package/src/index.ts +0 -17
  63. package/src/logger.ts +0 -169
  64. package/src/memoria.ts +0 -437
  65. package/src/models.ts +0 -356
  66. package/src/native-tools.ts +0 -73
  67. package/src/relaunch.ts +0 -67
  68. package/src/stall-phase.ts +0 -20
  69. package/src/tool-outputs.ts +0 -42
  70. package/tests/guards/types-test-lane-manifest.contract.test.ts +0 -42
  71. package/tests/logger.test.ts +0 -102
  72. package/tests/memoria-vitest-isolation.contract.test.ts +0 -108
  73. package/tests/test-lane-manifest.ts +0 -177
  74. package/tests/vitest-project-runner.contract.test.ts +0 -309
  75. package/tests/vitest-runner-capacity.contract.test.ts +0 -142
  76. package/tests/vitest-shared-pool.contract.test.ts +0 -79
  77. package/tsconfig.cjs.json +0 -15
  78. package/tsconfig.json +0 -12
  79. package/vitest.config.ts +0 -17
@@ -1,102 +0,0 @@
1
- import { describe, it, expect, vi, afterEach, beforeEach } from "vitest";
2
- import { log } from "../src/logger.js";
3
- import type { LogLevel } from "../src/logger.js";
4
-
5
- describe("leveled logger", () => {
6
- let originalLevel: LogLevel;
7
-
8
- beforeEach(() => {
9
- originalLevel = log.getLevel();
10
- // Reset to default "info" before each test
11
- log.setLevel("info");
12
- });
13
-
14
- afterEach(() => {
15
- log.setLevel(originalLevel);
16
- vi.restoreAllMocks();
17
- });
18
-
19
- it('default level is "info" — log.debug() does NOT call console.debug', () => {
20
- const spy = vi.spyOn(console, "debug").mockImplementation(() => {});
21
- log.debug("should be suppressed");
22
- expect(spy).not.toHaveBeenCalled();
23
- });
24
-
25
- it('default level "info" — log.info() DOES call console.info', () => {
26
- const spy = vi.spyOn(console, "info").mockImplementation(() => {});
27
- log.info("hello from info");
28
- expect(spy).toHaveBeenCalledWith("hello from info");
29
- });
30
-
31
- it('default level "info" — log.warn() and log.error() work', () => {
32
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
33
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
34
-
35
- log.warn("a warning");
36
- log.error("an error");
37
-
38
- expect(warnSpy).toHaveBeenCalledWith("a warning");
39
- expect(errorSpy).toHaveBeenCalledWith("an error");
40
- });
41
-
42
- it('after setLevel("debug") — log.debug() DOES call console.debug', () => {
43
- log.setLevel("debug");
44
- const spy = vi.spyOn(console, "debug").mockImplementation(() => {});
45
- log.debug("debug message");
46
- expect(spy).toHaveBeenCalledWith("debug message");
47
- });
48
-
49
- it('after setLevel("silent") — nothing calls console methods', () => {
50
- log.setLevel("silent");
51
-
52
- const debugSpy = vi.spyOn(console, "debug").mockImplementation(() => {});
53
- const infoSpy = vi.spyOn(console, "info").mockImplementation(() => {});
54
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
55
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
56
-
57
- log.debug("nope");
58
- log.info("nope");
59
- log.warn("nope");
60
- log.error("nope");
61
-
62
- expect(debugSpy).not.toHaveBeenCalled();
63
- expect(infoSpy).not.toHaveBeenCalled();
64
- expect(warnSpy).not.toHaveBeenCalled();
65
- expect(errorSpy).not.toHaveBeenCalled();
66
- });
67
-
68
- it("swallows broken-pipe logger writes", () => {
69
- const brokenPipe = Object.assign(new Error("broken pipe"), { code: "EPIPE" });
70
- vi.spyOn(console, "warn").mockImplementation(() => {
71
- throw brokenPipe;
72
- });
73
- vi.spyOn(console, "error").mockImplementation(() => {
74
- throw brokenPipe;
75
- });
76
-
77
- expect(() => log.warn("non-fatal warn")).not.toThrow();
78
- expect(() => log.error("non-fatal error")).not.toThrow();
79
- });
80
-
81
- it("rethrows non-broken-pipe logger write failures", () => {
82
- const unexpected = new Error("unexpected logger failure");
83
- vi.spyOn(console, "warn").mockImplementation(() => {
84
- throw unexpected;
85
- });
86
-
87
- expect(() => log.warn("should explode")).toThrow("unexpected logger failure");
88
- });
89
-
90
- it("getLevel() returns the current level", () => {
91
- expect(log.getLevel()).toBe("info");
92
-
93
- log.setLevel("debug");
94
- expect(log.getLevel()).toBe("debug");
95
-
96
- log.setLevel("error");
97
- expect(log.getLevel()).toBe("error");
98
-
99
- log.setLevel("silent");
100
- expect(log.getLevel()).toBe("silent");
101
- });
102
- });
@@ -1,108 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
-
3
- const FILE_ISOLATED_TARGET_ENV = "ARIA_VITEST_FILE_ISOLATED_TARGET";
4
- const ORIGINAL_FILE_ISOLATED_TARGET = process.env[FILE_ISOLATED_TARGET_ENV];
5
- const VITEST_UNIT_ONLY_ENV = "VITEST_UNIT_ONLY";
6
- const ORIGINAL_VITEST_UNIT_ONLY = process.env[VITEST_UNIT_ONLY_ENV];
7
-
8
- async function importRunner() {
9
- vi.resetModules();
10
- return import(new URL("../../../scripts/run-vitest-projects.mjs", import.meta.url).href);
11
- }
12
-
13
- async function importMemoriaVitestConfig() {
14
- vi.resetModules();
15
- return import(new URL("../../../packages/memoria/vitest.config.ts", import.meta.url).href);
16
- }
17
-
18
- afterEach(() => {
19
- vi.resetModules();
20
- if (ORIGINAL_FILE_ISOLATED_TARGET === undefined) {
21
- delete process.env[FILE_ISOLATED_TARGET_ENV];
22
- } else {
23
- process.env[FILE_ISOLATED_TARGET_ENV] = ORIGINAL_FILE_ISOLATED_TARGET;
24
- }
25
- if (ORIGINAL_VITEST_UNIT_ONLY === undefined) {
26
- delete process.env[VITEST_UNIT_ONLY_ENV];
27
- } else {
28
- process.env[VITEST_UNIT_ONLY_ENV] = ORIGINAL_VITEST_UNIT_ONLY;
29
- }
30
- });
31
-
32
- describe("memoria vitest isolation contract", () => {
33
- it("propagates the full-suite Vitest concurrency budget to child package runs", async () => {
34
- const { buildVitestSpawnEnv } = await importRunner();
35
-
36
- const env = buildVitestSpawnEnv("packages/server/vitest.config.ts", [], {}, 11);
37
-
38
- expect(env.VITEST_CONCURRENT_PROCESSES).toBe("11");
39
- });
40
-
41
- it("does not mark memoria file targets as isolated shards now that file isolation is removed", async () => {
42
- const { buildVitestSpawnEnv } = await importRunner();
43
-
44
- const env = buildVitestSpawnEnv(
45
- "packages/memoria/vitest.config.ts",
46
- ["tests/storage/sqlite.test.ts", "--passWithNoTests"],
47
- {},
48
- 11,
49
- );
50
-
51
- expect(env.ARIA_VITEST_FILE_ISOLATED_TARGET).toBeUndefined();
52
- });
53
-
54
- it("does not mark package-wide memoria runs as isolated file shards", async () => {
55
- const { buildVitestSpawnEnv } = await importRunner();
56
-
57
- const env = buildVitestSpawnEnv(
58
- "packages/memoria/vitest.config.ts",
59
- ["--passWithNoTests"],
60
- {},
61
- 11,
62
- );
63
-
64
- expect(env.ARIA_VITEST_FILE_ISOLATED_TARGET).toBeUndefined();
65
- });
66
-
67
- it("treats explicit memoria file targets as regular args since file isolation is removed", async () => {
68
- const { getVitestTargets } = await importRunner();
69
-
70
- expect(
71
- getVitestTargets("packages/memoria/vitest.config.ts", [
72
- "tests/apr-evidence-gate.test.ts",
73
- "--passWithNoTests",
74
- ]),
75
- ).toEqual([[]]);
76
- });
77
-
78
- it("keeps forks when the outer runner already isolates memoria to one file", async () => {
79
- process.env[FILE_ISOLATED_TARGET_ENV] = "1";
80
- delete process.env[VITEST_UNIT_ONLY_ENV];
81
-
82
- const { default: config } = await importMemoriaVitestConfig();
83
-
84
- expect(config.test?.pool).toBe("forks");
85
- expect(config.test?.maxWorkers).toBeGreaterThanOrEqual(2);
86
- });
87
-
88
- it("uses threads with parallel workers for package-wide memoria unit runs", async () => {
89
- delete process.env[FILE_ISOLATED_TARGET_ENV];
90
- process.env[VITEST_UNIT_ONLY_ENV] = "true";
91
-
92
- const { default: config } = await importMemoriaVitestConfig();
93
-
94
- expect(config.test?.pool).toBe("threads");
95
- expect(config.test?.maxWorkers).toBeGreaterThanOrEqual(2);
96
- });
97
-
98
- it("keeps fork isolation for package-wide non-unit memoria config factories", async () => {
99
- delete process.env[FILE_ISOLATED_TARGET_ENV];
100
- delete process.env[VITEST_UNIT_ONLY_ENV];
101
-
102
- const { createMemoriaVitestConfig } = await importMemoriaVitestConfig();
103
- const config = createMemoriaVitestConfig(false);
104
-
105
- expect(config.test?.pool).toBe("forks");
106
- expect(config.test?.maxWorkers).toBe(1);
107
- });
108
- });
@@ -1,177 +0,0 @@
1
- import { readdirSync, statSync } from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { getMaxWorkers } from "../../../vitest.shared.ts";
5
-
6
- export type TypesTestLaneId = "unit";
7
- export type TypesTestProofClassId = "gate" | "system" | "live";
8
-
9
- export interface TypesTestPlacement {
10
- lane: TypesTestLaneId;
11
- proofClass: TypesTestProofClassId;
12
- resourceKeys: string[];
13
- }
14
-
15
- export interface TypesTestLaneManifest {
16
- defaultLane: "unit";
17
- lanes: Record<TypesTestLaneId, string[]>;
18
- setupFiles: Record<TypesTestLaneId, string[]>;
19
- maxWorkers: Record<TypesTestLaneId, number>;
20
- pool: Record<TypesTestLaneId, "forks">;
21
- fileParallelism: Record<TypesTestLaneId, boolean>;
22
- unclassified: string[];
23
- proofClasses: Record<TypesTestProofClassId, string[]>;
24
- resourceKeysByFile: Record<string, string[]>;
25
- unclassifiedProofClasses: string[];
26
- }
27
-
28
- const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
29
- const scanRoots = [path.join(packageRoot, "tests"), path.join(packageRoot, "src")];
30
- const EMPTY_SETUP: string[] = [];
31
- const SAFE_UNIT_WORKERS = Math.min(4, getMaxWorkers());
32
-
33
- function normalizeRelativePath(filePath: string): string {
34
- return path.relative(packageRoot, filePath).split(path.sep).join("/");
35
- }
36
-
37
- function statSafe(targetPath: string): ReturnType<typeof statSync> | null {
38
- try {
39
- return statSync(targetPath);
40
- } catch {
41
- return null;
42
- }
43
- }
44
-
45
- function collectTestFiles(dir: string, out: string[]): void {
46
- if (!statSafe(dir)?.isDirectory()) return;
47
-
48
- for (const entry of readdirSync(dir)) {
49
- if (entry === "node_modules" || entry === "dist" || entry === ".worktrees") continue;
50
-
51
- const fullPath = path.join(dir, entry);
52
- const stat = statSync(fullPath);
53
- if (stat.isDirectory()) {
54
- collectTestFiles(fullPath, out);
55
- continue;
56
- }
57
-
58
- if (entry.endsWith(".test.ts") || entry.endsWith(".test.tsx")) {
59
- out.push(normalizeRelativePath(fullPath));
60
- }
61
- }
62
- }
63
-
64
- function isTestFile(relativePath: string): boolean {
65
- return relativePath.endsWith(".test.ts") || relativePath.endsWith(".test.tsx");
66
- }
67
-
68
- export function classifyTypesTestPlacement(relativePath: string): TypesTestPlacement | null {
69
- const normalized = relativePath.split(path.sep).join("/");
70
- if (!isTestFile(normalized)) {
71
- return null;
72
- }
73
-
74
- return {
75
- lane: "unit",
76
- proofClass: "gate",
77
- resourceKeys: [],
78
- };
79
- }
80
-
81
- export function classifyTypesTestFile(relativePath: string): TypesTestLaneId | null {
82
- return classifyTypesTestPlacement(relativePath)?.lane ?? null;
83
- }
84
-
85
- export function classifyTypesTestProofClass(relativePath: string): TypesTestProofClassId | null {
86
- return classifyTypesTestPlacement(relativePath)?.proofClass ?? null;
87
- }
88
-
89
- export function loadTypesTestLaneManifest(): TypesTestLaneManifest {
90
- const files: string[] = [];
91
- for (const scanRoot of scanRoots) {
92
- collectTestFiles(scanRoot, files);
93
- }
94
-
95
- const manifest: TypesTestLaneManifest = {
96
- defaultLane: "unit",
97
- lanes: {
98
- unit: [],
99
- },
100
- setupFiles: {
101
- unit: EMPTY_SETUP,
102
- },
103
- maxWorkers: {
104
- unit: SAFE_UNIT_WORKERS,
105
- },
106
- pool: {
107
- unit: "forks",
108
- },
109
- fileParallelism: {
110
- unit: true,
111
- },
112
- unclassified: [],
113
- proofClasses: {
114
- gate: [],
115
- system: [],
116
- live: [],
117
- },
118
- resourceKeysByFile: {},
119
- unclassifiedProofClasses: [],
120
- };
121
-
122
- for (const relativePath of files.sort()) {
123
- const placement = classifyTypesTestPlacement(relativePath);
124
- if (!placement) {
125
- manifest.unclassified.push(relativePath);
126
- manifest.unclassifiedProofClasses.push(relativePath);
127
- continue;
128
- }
129
-
130
- manifest.lanes[placement.lane].push(relativePath);
131
- manifest.proofClasses[placement.proofClass].push(relativePath);
132
- manifest.resourceKeysByFile[relativePath] = [...placement.resourceKeys];
133
- }
134
-
135
- return manifest;
136
- }
137
-
138
- export function getTypesTestLaneFiles(lane: TypesTestLaneId): string[] {
139
- return [...loadTypesTestLaneManifest().lanes[lane]];
140
- }
141
-
142
- export function getTypesTestLaneSetupFiles(lane: TypesTestLaneId): string[] {
143
- return [...loadTypesTestLaneManifest().setupFiles[lane]];
144
- }
145
-
146
- export function getTypesTestLaneMaxWorkers(lane: TypesTestLaneId): number {
147
- return loadTypesTestLaneManifest().maxWorkers[lane];
148
- }
149
-
150
- export function getTypesTestLanePool(lane: TypesTestLaneId): "forks" {
151
- return loadTypesTestLaneManifest().pool[lane];
152
- }
153
-
154
- export function getTypesTestLaneFileParallelism(lane: TypesTestLaneId): boolean {
155
- return loadTypesTestLaneManifest().fileParallelism[lane];
156
- }
157
-
158
- export function getTypesDiagnosticBreadthFiles(
159
- view: "unit" | "integration" | "e2e" | "native",
160
- ): string[] {
161
- return view === "unit" ? getTypesTestLaneFiles("unit") : [];
162
- }
163
-
164
- export function getTypesTestProofClassFiles(proofClass: TypesTestProofClassId): string[] {
165
- return [...loadTypesTestLaneManifest().proofClasses[proofClass]];
166
- }
167
-
168
- export const PACKAGE_TEST_AUTHORITY = {
169
- classifyPlacement: classifyTypesTestPlacement,
170
- classifyLane: classifyTypesTestFile,
171
- classifyProofClass: classifyTypesTestProofClass,
172
- loadManifest: loadTypesTestLaneManifest,
173
- getLaneFiles: getTypesTestLaneFiles,
174
- getLaneGlobalSetup: () => [],
175
- getDiagnosticBreadthFiles: getTypesDiagnosticBreadthFiles,
176
- getProofClassFiles: getTypesTestProofClassFiles,
177
- };
@@ -1,309 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { EventEmitter } from "node:events";
4
- import { PassThrough } from "node:stream";
5
- import { afterEach, describe, expect, it, vi } from "vitest";
6
-
7
- async function importRunner() {
8
- vi.resetModules();
9
- return import(new URL("../../../scripts/run-vitest-projects.mjs", import.meta.url).href);
10
- }
11
-
12
- afterEach(() => {
13
- vi.restoreAllMocks();
14
- vi.resetModules();
15
- vi.doUnmock("node:child_process");
16
- });
17
-
18
- class FakeChild extends EventEmitter {
19
- stdout = new PassThrough();
20
- stderr = new PassThrough();
21
- pid = 0;
22
- exitCode: number | null = null;
23
- signalCode: NodeJS.Signals | null = null;
24
- unrefCalls = 0;
25
-
26
- kill(_signal?: NodeJS.Signals | number): boolean {
27
- return true;
28
- }
29
-
30
- unref(): this {
31
- this.unrefCalls += 1;
32
- return this;
33
- }
34
- }
35
-
36
- describe("vitest project runner", () => {
37
- it("enumerates every package vitest config in deterministic order", async () => {
38
- const { PROJECT_VITEST_CONFIGS } = await importRunner();
39
-
40
- expect(PROJECT_VITEST_CONFIGS).toEqual([
41
- "packages/types/vitest.config.ts",
42
- "packages/models/vitest.config.ts",
43
- "packages/memoria/vitest.config.ts",
44
- "packages/memoria-bridge/vitest.config.ts",
45
- "packages/tools/vitest.config.ts",
46
- "packages/wireguard/vitest.config.ts",
47
- "packages/aria/vitest.config.ts",
48
- "packages/auth/vitest.config.ts",
49
- "packages/benchmark/vitest.config.ts",
50
- "packages/server/vitest.config.ts",
51
- "packages/cli/vitest.config.ts",
52
- ]);
53
- });
54
-
55
- it("enumerates every package integration vitest config in deterministic order and excludes packages without an integration lane", async () => {
56
- const { PROJECT_INTEGRATION_VITEST_CONFIGS } = await importRunner();
57
-
58
- expect(PROJECT_INTEGRATION_VITEST_CONFIGS).toEqual([
59
- "packages/models/vitest.integration.config.ts",
60
- "packages/memoria/vitest.integration.config.ts",
61
- "packages/memoria-bridge/vitest.integration.config.ts",
62
- "packages/tools/vitest.integration.config.ts",
63
- "packages/wireguard/vitest.integration.config.ts",
64
- "packages/aria/vitest.integration.config.ts",
65
- "packages/auth/vitest.integration.config.ts",
66
- "packages/benchmark/vitest.integration.config.ts",
67
- "packages/server/vitest.integration.config.ts",
68
- "packages/cli/vitest.integration.config.ts",
69
- ]);
70
- expect(PROJECT_INTEGRATION_VITEST_CONFIGS).not.toContain(
71
- "packages/types/vitest.integration.config.ts",
72
- );
73
- });
74
-
75
- it("runs package suites sequentially and stops on the first failure", async () => {
76
- const { runProjectSuites } = await importRunner();
77
- const calls: string[] = [];
78
-
79
- const exitCode = await runProjectSuites(
80
- ["first", "second", "third"],
81
- ["--passWithNoTests"],
82
- async (configPath, args) => {
83
- calls.push(`${configPath} ${args.join(" ")}`);
84
- return configPath === "second" ? 1 : 0;
85
- },
86
- );
87
-
88
- expect(exitCode).toBe(1);
89
- expect(calls).toEqual(["first --passWithNoTests", "second --passWithNoTests"]);
90
- });
91
-
92
- it("launches each vitest config from its package root so config-relative paths stay local", async () => {
93
- const { buildVitestInvocation } = await importRunner();
94
-
95
- expect(
96
- buildVitestInvocation("packages/server/vitest.config.ts", ["--passWithNoTests"]),
97
- ).toEqual({
98
- cwd: "packages/server",
99
- args: ["run", "--config", "vitest.config.ts", "--passWithNoTests"],
100
- });
101
- });
102
-
103
- it("runs memoria as a single vitest invocation instead of per-file splits", async () => {
104
- const { getVitestTargets } = await importRunner();
105
-
106
- const targets = getVitestTargets("packages/memoria/vitest.config.ts", ["--passWithNoTests"]);
107
-
108
- expect(targets).toEqual([[]]);
109
- });
110
-
111
- it("keeps coverage runs as a single package invocation so Vitest can aggregate coverage", async () => {
112
- const { getVitestTargets } = await importRunner();
113
-
114
- expect(getVitestTargets("packages/memoria/vitest.config.ts", ["--coverage"])).toEqual([[]]);
115
- });
116
-
117
- it("passes explicit memoria file targets through exactly once instead of expanding the package", async () => {
118
- const { runProjectSuites } = await importRunner();
119
- const calls: string[] = [];
120
-
121
- const exitCode = await runProjectSuites(
122
- ["packages/memoria/vitest.config.ts"],
123
- ["tests/apr-evidence-gate.test.ts", "--passWithNoTests"],
124
- async (_configPath, args) => {
125
- calls.push(args.join(" "));
126
- return 0;
127
- },
128
- );
129
-
130
- expect(exitCode).toBe(0);
131
- expect(calls).toEqual(["tests/apr-evidence-gate.test.ts --passWithNoTests"]);
132
- });
133
-
134
- it("reports a failing package with config context", async () => {
135
- const { runProjectSuites } = await importRunner();
136
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
137
-
138
- const exitCode = await runProjectSuites(
139
- ["packages/memoria/vitest.config.ts"],
140
- ["--passWithNoTests"],
141
- async () => 1,
142
- );
143
-
144
- expect(exitCode).toBe(1);
145
- expect(errorSpy).toHaveBeenCalledWith(
146
- expect.stringContaining("packages/memoria/vitest.config.ts"),
147
- );
148
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("exit code 1"));
149
- });
150
-
151
- it("reports child signal failures instead of collapsing them into an anonymous nonzero exit", async () => {
152
- const { runProjectSuites } = await importRunner();
153
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
154
-
155
- const exitCode = await runProjectSuites(["packages/server/vitest.config.ts"], [], async () => ({
156
- exitCode: 1,
157
- signal: "SIGTERM",
158
- }));
159
-
160
- expect(exitCode).toBe(1);
161
- expect(errorSpy).toHaveBeenCalledWith(
162
- expect.stringContaining("packages/server/vitest.config.ts"),
163
- );
164
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("signal SIGTERM"));
165
- });
166
-
167
- it("derives the vitest lease from the active absolute git dir so sibling worktrees do not serialize package lanes", async () => {
168
- const { vitestRunnerLeaseDir } = await importRunner();
169
- const runnerSource = fs.readFileSync(
170
- path.resolve(import.meta.dirname, "../../../scripts/run-vitest-projects.mjs"),
171
- "utf8",
172
- );
173
-
174
- expect(runnerSource.includes('["rev-parse", "--absolute-git-dir"]')).toBe(true);
175
- expect(runnerSource.includes('["rev-parse", "--git-common-dir"]')).toBe(false);
176
-
177
- expect(
178
- vitestRunnerLeaseDir("/repo/.worktrees/feature", () => "/repo/.git/worktrees/feature"),
179
- ).toBe(
180
- "/repo/.git/worktrees/feature/.vitest-runner.lock",
181
- );
182
-
183
- expect(
184
- vitestRunnerLeaseDir("/repo/.worktrees/feature", () => "/repo/.git/worktrees/feature"),
185
- ).toBe("/repo/.git/worktrees/feature/.vitest-runner.lock");
186
- expect(vitestRunnerLeaseDir("/repo", () => "/repo/.git")).toBe(
187
- "/repo/.git/.vitest-runner.lock",
188
- );
189
- expect(runnerSource.includes('["rev-parse", "--absolute-git-dir"]')).toBe(true);
190
- expect(runnerSource.includes('["rev-parse", "--git-common-dir"]')).toBe(false);
191
- });
192
-
193
- it("resolves the active absolute git dir instead of the shared common dir for linked worktrees", async () => {
194
- const { resolveGitDir } = await importRunner();
195
- const exec = vi.fn(() => "/repo/.git/worktrees/feature\n");
196
-
197
- expect(resolveGitDir("/repo/.worktrees/feature", exec)).toBe("/repo/.git/worktrees/feature");
198
- expect(exec).toHaveBeenCalledWith("git", ["rev-parse", "--absolute-git-dir"], {
199
- cwd: "/repo/.worktrees/feature",
200
- encoding: "utf8",
201
- });
202
- });
203
-
204
- it("routes cli suite execution through the worktree-local vitest lease", async () => {
205
- const { runVitestProjectsCli } = await importRunner();
206
- const withVitestRunnerLease = vi.fn(async (action: () => Promise<number>) => await action());
207
- const runCommand = vi.fn(async () => 0);
208
-
209
- const exitCode = await runVitestProjectsCli(
210
- ["packages/types/vitest.config.ts", "--passWithNoTests"],
211
- {
212
- withVitestRunnerLease,
213
- runCommand,
214
- },
215
- );
216
-
217
- expect(exitCode).toBe(0);
218
- expect(withVitestRunnerLease).toHaveBeenCalledTimes(1);
219
- expect(runCommand).toHaveBeenCalledTimes(1);
220
- });
221
-
222
- it("uses a bounded vitest lease wait with worktree-local metadata instead of an unbounded shared lease", async () => {
223
- const { withVitestRunnerLease } = await importRunner();
224
- const withDirectoryLockImpl = vi.fn(
225
- async (
226
- lockDir: string,
227
- action: () => Promise<number>,
228
- options?: {
229
- timeoutMs?: number | null;
230
- lockDescription?: string;
231
- metadata?: Record<string, unknown>;
232
- },
233
- ) => {
234
- expect(lockDir).toBe("/repo/.git/worktrees/feature/.vitest-runner.lock");
235
- expect(options?.lockDescription).toBe("vitest runner lease");
236
- expect(options?.metadata).toEqual({
237
- purpose: "vitest-runner",
238
- root: "/repo/.worktrees/feature",
239
- });
240
- expect(typeof options?.timeoutMs).toBe("number");
241
- expect((options?.timeoutMs ?? 0) > 0).toBe(true);
242
- return await action();
243
- },
244
- );
245
-
246
- const result = await withVitestRunnerLease(async () => 7, {
247
- repoRoot: "/repo/.worktrees/feature",
248
- lockDir: "/repo/.git/worktrees/feature/.vitest-runner.lock",
249
- withDirectoryLockImpl,
250
- });
251
-
252
- expect(result).toBe(7);
253
- expect(withDirectoryLockImpl).toHaveBeenCalledTimes(1);
254
- });
255
-
256
- it("resolves the Vitest binary through node_modules/.bin instead of a hardcoded package path", async () => {
257
- const { vitestBinaryPath } = await importRunner();
258
- const expectedSuffix =
259
- process.platform === "win32" ? "node_modules/.bin/vitest.cmd" : "node_modules/.bin/vitest";
260
-
261
- expect(vitestBinaryPath("/repo")).toBe("/repo/" + expectedSuffix);
262
- });
263
-
264
- it("resolves detached vitest children on exit without waiting for close", async () => {
265
- const child = new FakeChild();
266
- let spawned = false;
267
- const { spawnVitest } = await importRunner();
268
- const resultPromise = spawnVitest("packages/server/vitest.config.ts", ["--passWithNoTests"], {
269
- waitForCapacityImpl: vi.fn().mockResolvedValue(undefined),
270
- spawnImpl: () => {
271
- spawned = true;
272
- return child as never;
273
- },
274
- });
275
-
276
- await Promise.resolve();
277
- expect(spawned).toBe(true);
278
- child.stdout.write("partial stdout\n");
279
- child.exitCode = 0;
280
- child.emit("exit", 0, null);
281
-
282
- const result = await Promise.race([
283
- resultPromise,
284
- new Promise<never>((_, reject) =>
285
- setTimeout(() => reject(new Error("spawnVitest did not resolve on exit")), 100),
286
- ),
287
- ]);
288
-
289
- expect(result).toEqual({ exitCode: 0, signal: null });
290
- expect(child.stdout.destroyed).toBe(true);
291
- expect(child.stderr.destroyed).toBe(true);
292
- expect(child.unrefCalls).toBe(1);
293
- });
294
-
295
- it("preserves detached vitest child signals as conventional shell status", async () => {
296
- const child = new FakeChild();
297
- const { spawnVitest } = await importRunner();
298
- const resultPromise = spawnVitest("packages/server/vitest.config.ts", ["--passWithNoTests"], {
299
- waitForCapacityImpl: vi.fn().mockResolvedValue(undefined),
300
- spawnImpl: () => child as never,
301
- });
302
-
303
- await Promise.resolve();
304
- child.signalCode = "SIGTERM";
305
- child.emit("exit", null, "SIGTERM");
306
-
307
- await expect(resultPromise).resolves.toEqual({ exitCode: 143, signal: "SIGTERM" });
308
- });
309
- });