@elench/testkit 0.1.108 → 0.1.110

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 (69) hide show
  1. package/README.md +9 -9
  2. package/lib/app/doctor.mjs +5 -5
  3. package/lib/app/typecheck.mjs +6 -5
  4. package/lib/bundler/index.mjs +134 -7
  5. package/lib/cli/args.mjs +3 -2
  6. package/lib/cli/assistant/app.mjs +19 -5
  7. package/lib/cli/assistant/command-observer.mjs +2 -1
  8. package/lib/cli/assistant/command-results.mjs +2 -1
  9. package/lib/cli/assistant/context-pack.mjs +2 -2
  10. package/lib/cli/assistant/prompt-builder.mjs +2 -2
  11. package/lib/cli/assistant/quality-signal-strip.mjs +103 -0
  12. package/lib/cli/assistant/transcript-text.mjs +2 -1
  13. package/lib/cli/assistant/view-model.mjs +79 -0
  14. package/lib/cli/command-flags.mjs +2 -1
  15. package/lib/cli/commands/cleanup.mjs +13 -2
  16. package/lib/cli/commands/discover.mjs +2 -1
  17. package/lib/cli/commands/run.mjs +3 -2
  18. package/lib/cli/entrypoint.mjs +3 -1
  19. package/lib/cli/operations/cleanup/operation.mjs +6 -1
  20. package/lib/cli/operations/status/operation.mjs +2 -2
  21. package/lib/cli/renderers/discover/report.mjs +6 -8
  22. package/lib/cli/renderers/run/failure.mjs +1 -1
  23. package/lib/cli/renderers/run/text-reporter.mjs +1 -1
  24. package/lib/cli/renderers/status/text.mjs +101 -1
  25. package/lib/config/discovery.mjs +10 -1
  26. package/lib/config-api/index.mjs +2 -2
  27. package/lib/config-api/next-runtime-tsconfig.mjs +2 -1
  28. package/lib/coverage/graph-builder.mjs +2 -4
  29. package/lib/coverage/routing.mjs +1 -1
  30. package/lib/coverage/shared.mjs +1 -2
  31. package/lib/discovery/index.d.ts +5 -8
  32. package/lib/discovery/index.mjs +15 -24
  33. package/lib/domain/test-types.mjs +44 -0
  34. package/lib/history/index.d.ts +3 -4
  35. package/lib/history/index.mjs +6 -14
  36. package/lib/runner/formatting.mjs +2 -3
  37. package/lib/runner/maintenance.mjs +136 -35
  38. package/lib/runner/planning.mjs +1 -1
  39. package/lib/runner/results.mjs +0 -6
  40. package/lib/runner/status-model.mjs +520 -0
  41. package/lib/runner/suite-selection.mjs +20 -11
  42. package/lib/runner/template-steps.mjs +2 -2
  43. package/lib/runner/template.mjs +4 -0
  44. package/lib/ui/index.d.ts +1 -0
  45. package/lib/ui/index.mjs +1 -0
  46. package/lib/vitest/index.mjs +2 -1
  47. package/node_modules/@elench/next-analysis/package.json +1 -1
  48. package/node_modules/@elench/testkit-bridge/dist/index.js +9 -11
  49. package/node_modules/@elench/testkit-bridge/dist/index.js.map +1 -1
  50. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  51. package/node_modules/@elench/testkit-protocol/dist/index.d.ts +1 -3
  52. package/node_modules/@elench/testkit-protocol/dist/index.d.ts.map +1 -1
  53. package/node_modules/@elench/testkit-protocol/dist/index.js +3 -6
  54. package/node_modules/@elench/testkit-protocol/dist/index.js.map +1 -1
  55. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  56. package/node_modules/@elench/ts-analysis/dist/requests.js +1 -1
  57. package/node_modules/@elench/ts-analysis/package.json +1 -1
  58. package/package.json +9 -9
  59. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.d.ts +188 -0
  60. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.d.ts.map +1 -0
  61. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.js +293 -0
  62. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.js.map +1 -0
  63. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/package.json +25 -0
  64. package/node_modules/es-toolkit/CHANGELOG.md +0 -801
  65. package/node_modules/es-toolkit/src/compat/_internal/Equals.d.ts +0 -1
  66. package/node_modules/es-toolkit/src/compat/_internal/IsWritable.d.ts +0 -3
  67. package/node_modules/es-toolkit/src/compat/_internal/MutableList.d.ts +0 -4
  68. package/node_modules/es-toolkit/src/compat/_internal/RejectReadonly.d.ts +0 -4
  69. package/node_modules/esprima/ChangeLog +0 -235
@@ -0,0 +1,44 @@
1
+ export const TEST_TYPE_ORDER = ["ui", "e2e", "scenario", "int", "dal", "load"];
2
+ export const TEST_TYPES = new Set(TEST_TYPE_ORDER);
3
+ export const RUN_TYPE_ORDER = [...TEST_TYPE_ORDER, "all"];
4
+ export const RUN_TYPES = new Set(RUN_TYPE_ORDER);
5
+ export const LEGACY_TEST_TYPE_ALIASES = new Map([
6
+ ["pw", "ui"],
7
+ ]);
8
+
9
+ const TEST_TYPE_LABELS = {
10
+ ui: "UI",
11
+ e2e: "E2E",
12
+ scenario: "Scenario",
13
+ int: "INT",
14
+ dal: "DAL",
15
+ load: "Load",
16
+ };
17
+
18
+ export function normalizePublicTestType(value) {
19
+ const normalized = String(value || "").trim();
20
+ return LEGACY_TEST_TYPE_ALIASES.get(normalized) || normalized;
21
+ }
22
+
23
+ export function isPublicTestType(value) {
24
+ return TEST_TYPES.has(normalizePublicTestType(value));
25
+ }
26
+
27
+ export function isRunType(value) {
28
+ const normalized = normalizePublicTestType(value);
29
+ return normalized === "all" || TEST_TYPES.has(normalized);
30
+ }
31
+
32
+ export function formatPublicTestType(value) {
33
+ const normalized = normalizePublicTestType(value);
34
+ return TEST_TYPE_LABELS[normalized] || String(value || "");
35
+ }
36
+
37
+ export function publicTestTypeList({ includeAll = false, includeLegacy = false } = {}) {
38
+ const values = includeAll ? RUN_TYPE_ORDER : TEST_TYPE_ORDER;
39
+ return includeLegacy ? [...values, "pw"] : [...values];
40
+ }
41
+
42
+ export function publicTestTypeListText(options = {}) {
43
+ return publicTestTypeList(options).join(", ");
44
+ }
@@ -15,8 +15,7 @@ export interface TestHistoryEntry extends TestHistorySummary {
15
15
  path: string;
16
16
  service: string;
17
17
  suiteName: string;
18
- selectionType: string;
19
- framework: string;
18
+ type: string;
20
19
  notRunCount: number;
21
20
  durationCount: number;
22
21
  }
@@ -37,10 +36,10 @@ export declare function updateHistoryFromRunArtifact(
37
36
  ): TestHistoryDocument;
38
37
  export declare function summarizeHistoryForFiles(
39
38
  history: TestHistoryDocument,
40
- files?: Array<{ id: string; service: string; selectionType: string; path: string }>
39
+ files?: Array<{ id: string; service: string; type: string; path: string }>
41
40
  ): Map<string, TestHistorySummary>;
42
41
  export declare function buildHistoryTestId(
43
42
  serviceName: string,
44
- selectionType: string,
43
+ type: string,
45
44
  filePath: string
46
45
  ): string;
@@ -52,8 +52,7 @@ export function updateHistoryFromRunArtifact(history, runArtifact, recordedAt =
52
52
  path: file.path,
53
53
  service: service.name,
54
54
  suiteName: suite.name,
55
- selectionType: suite.type,
56
- framework: normalizeArtifactFramework(suite.framework),
55
+ type: suite.type,
57
56
  firstSeenAt: seenAt,
58
57
  lastSeenAt: seenAt,
59
58
  lastRunAt: seenAt,
@@ -70,8 +69,7 @@ export function updateHistoryFromRunArtifact(history, runArtifact, recordedAt =
70
69
  next.path = file.path;
71
70
  next.service = service.name;
72
71
  next.suiteName = suite.name;
73
- next.selectionType = suite.type;
74
- next.framework = normalizeArtifactFramework(suite.framework);
72
+ next.type = suite.type;
75
73
  next.lastSeenAt = seenAt;
76
74
  next.lastRunAt = seenAt;
77
75
  next.runCount += 1;
@@ -106,7 +104,7 @@ export function summarizeHistoryForFiles(history, files = []) {
106
104
  const normalized = normalizeHistory(history);
107
105
  const byId = new Map();
108
106
  for (const file of files) {
109
- const entry = normalized.tests[file.id] || normalized.tests[buildHistoryTestId(file.service, file.selectionType, file.path)];
107
+ const entry = normalized.tests[file.id] || normalized.tests[buildHistoryTestId(file.service, file.type, file.path)];
110
108
  if (!entry) continue;
111
109
  byId.set(file.id, {
112
110
  firstSeenAt: entry.firstSeenAt || null,
@@ -123,8 +121,8 @@ export function summarizeHistoryForFiles(history, files = []) {
123
121
  return byId;
124
122
  }
125
123
 
126
- export function buildHistoryTestId(serviceName, selectionType, filePath) {
127
- return [serviceName, selectionType, normalizePath(filePath)].join("|");
124
+ export function buildHistoryTestId(serviceName, type, filePath) {
125
+ return [serviceName, type, normalizePath(filePath)].join("|");
128
126
  }
129
127
 
130
128
  function normalizeHistory(parsed) {
@@ -135,8 +133,7 @@ function normalizeHistory(parsed) {
135
133
  path: normalizePath(entry.path || ""),
136
134
  service: String(entry.service || ""),
137
135
  suiteName: String(entry.suiteName || ""),
138
- selectionType: String(entry.selectionType || ""),
139
- framework: normalizeArtifactFramework(entry.framework || "k6"),
136
+ type: String(entry.type || entry.selectionType || ""),
140
137
  firstSeenAt: entry.firstSeenAt || null,
141
138
  lastSeenAt: entry.lastSeenAt || null,
142
139
  lastRunAt: entry.lastRunAt || null,
@@ -156,11 +153,6 @@ function normalizeHistory(parsed) {
156
153
  };
157
154
  }
158
155
 
159
- function normalizeArtifactFramework(value) {
160
- if (value === "default") return "k6";
161
- return value || "k6";
162
- }
163
-
164
156
  function normalizePath(filePath) {
165
157
  return String(filePath).split(path.sep).join("/").replace(/^\.\/+/, "");
166
158
  }
@@ -40,8 +40,7 @@ export function longestServiceName(results) {
40
40
  }
41
41
 
42
42
  export function formatFrameworkLabel(framework) {
43
- if (!framework || framework === "k6") return "";
44
- return framework;
43
+ return "";
45
44
  }
46
45
 
47
46
  export function formatSuiteFramework(framework) {
@@ -175,7 +174,7 @@ export function buildDebugRunSummaryLines(results, durationMs, regressionReport
175
174
  const fileDetail =
176
175
  suite.failedFiles.length > 0 ? ` (${suite.failedFiles.join(", ")})` : "";
177
176
  lines.push(
178
- ` - ${suite.type}:${suite.name}${formatSuiteFramework(suite.framework)}${fileDetail} · ${formatDuration(suite.durationMs)}`
177
+ ` - ${suite.type}:${suite.name}${fileDetail} · ${formatDuration(suite.durationMs)}`
179
178
  );
180
179
  if (suite.error) {
181
180
  lines.push(` ${suite.error}`);
@@ -1,15 +1,14 @@
1
1
  import fs from "fs";
2
+ import path from "path";
2
3
  import {
3
4
  cleanupOrphanedLocalInfrastructure,
4
- collectServiceDatabaseStatus,
5
5
  destroyRuntimeDatabase,
6
6
  destroyServiceDatabaseCache,
7
7
  isDatabaseStateDir,
8
8
  } from "../database/index.mjs";
9
- import { cleanupRuns, formatRunSummary } from "./lifecycle.mjs";
10
- import { buildRunStatusLines } from "./readiness.mjs";
9
+ import { cleanupRuns, formatRunSummary, isPidRunning, listRunManifests } from "./lifecycle.mjs";
11
10
  import { findGraphDirsForService, findRuntimeStateDirs } from "./state.mjs";
12
- import { collectStateDirLines } from "./state-io.mjs";
11
+ import { collectCleanupTargets, collectStatusModel } from "./status-model.mjs";
13
12
 
14
13
  export async function destroy(config) {
15
14
  await cleanupRuns(config.productDir, { includeActive: true });
@@ -34,50 +33,152 @@ export async function destroy(config) {
34
33
  await cleanupOrphanedLocalInfrastructure(config.productDir);
35
34
  }
36
35
 
37
- export function showStatus(config) {
38
- const lines = [...buildRunStatusLines(config.productDir)];
39
- const graphDirs = findGraphDirsForService(config.productDir, config.name);
40
- const hasDirectState = fs.existsSync(config.stateDir);
41
- const hasGraphState = graphDirs.length > 0;
42
-
43
- if (!hasDirectState && !hasGraphState) {
44
- lines.push("No state run tests first.");
45
- } else {
46
- if (hasDirectState) {
47
- lines.push(" service-state/");
48
- lines.push(...collectStateDirLines(config.stateDir, " "));
36
+ export function showStatus(config, options = {}) {
37
+ return collectStatusModel(config, options);
38
+ }
39
+
40
+ export async function cleanup(productDir, options = {}) {
41
+ const dryRun = Boolean(options.dryRun);
42
+ const allConfigs = options.allConfigs || [];
43
+ const serviceName = options.serviceName || null;
44
+ const cache = normalizeCacheSelection(options.cache);
45
+ const summary = dryRun
46
+ ? collectRunCleanupPreview(productDir)
47
+ : await cleanupRuns(productDir, { includeActive: false });
48
+ const targets = collectCleanupTargets(productDir, {
49
+ allConfigs,
50
+ serviceName,
51
+ cache,
52
+ });
53
+
54
+ const runtimeCleaned = [];
55
+ const bundleCleaned = [];
56
+ const assistantCleaned = [];
57
+
58
+ if (!dryRun) {
59
+ for (const target of targets.runtime) {
60
+ await cleanupRuntimeDir(productDir, target.path);
61
+ runtimeCleaned.push(target);
62
+ }
63
+ for (const target of targets.bundles) {
64
+ fs.rmSync(target.path, { force: true });
65
+ bundleCleaned.push(target);
49
66
  }
50
- for (const graphDir of graphDirs) {
51
- lines.push(` graph-state/${graphDir.split("/").at(-1)}/`);
52
- lines.push(...collectStateDirLines(graphDir, " "));
67
+ for (const target of targets.assistant) {
68
+ fs.rmSync(target.path, { force: true });
69
+ assistantCleaned.push(target);
53
70
  }
71
+ pruneKnownEmptyDirs(productDir);
54
72
  }
55
73
 
56
- lines.push(...collectServiceDatabaseStatus(config.productDir, config.name));
57
- return {
58
- name: config.name,
59
- lines,
60
- };
61
- }
74
+ const lines = [];
75
+ for (const manifest of summary.cleaned) {
76
+ lines.push(`${dryRun ? "Would clean" : "Cleaned"} stale run ${formatRunSummary(manifest)}`);
77
+ }
78
+ for (const manifest of summary.skippedActive) {
79
+ lines.push(`Active run still present: ${formatRunSummary(manifest)}`);
80
+ }
81
+ for (const target of targets.runtime) {
82
+ lines.push(`${dryRun ? "Would remove" : "Removed"} stale runtime ${target.graph}/${target.runtimeId}`);
83
+ }
84
+ appendBoundedFileCleanupLines(lines, {
85
+ productDir,
86
+ targets: targets.bundles,
87
+ label: "bundle cache file",
88
+ dryRun,
89
+ });
90
+ appendBoundedFileCleanupLines(lines, {
91
+ productDir,
92
+ targets: targets.assistant,
93
+ label: "assistant command result",
94
+ dryRun,
95
+ });
62
96
 
63
- export async function cleanup(productDir) {
64
- const summary = await cleanupRuns(productDir, { includeActive: false });
65
- if (summary.cleaned.length === 0 && summary.skippedActive.length === 0) {
97
+ if (lines.length === 0) {
66
98
  return {
67
99
  ...summary,
100
+ dryRun,
101
+ targets,
102
+ runtimeCleaned,
103
+ bundleCleaned,
104
+ assistantCleaned,
68
105
  lines: ["No stale runs to clean."],
69
106
  };
70
107
  }
71
108
 
72
- const lines = [];
73
- for (const manifest of summary.cleaned) {
74
- lines.push(`Cleaned stale run ${formatRunSummary(manifest)}`);
75
- }
76
- for (const manifest of summary.skippedActive) {
77
- lines.push(`Active run still present: ${formatRunSummary(manifest)}`);
78
- }
79
109
  return {
80
110
  ...summary,
111
+ dryRun,
112
+ targets,
113
+ runtimeCleaned,
114
+ bundleCleaned,
115
+ assistantCleaned,
81
116
  lines,
82
117
  };
83
118
  }
119
+
120
+ async function cleanupRuntimeDir(productDir, runtimeDir) {
121
+ if (!runtimeDir || !fs.existsSync(runtimeDir)) return;
122
+ const runtimeStateDirs = findRuntimeStateDirs(runtimeDir, isDatabaseStateDir);
123
+ for (const stateDir of runtimeStateDirs) {
124
+ await destroyRuntimeDatabase({ productDir, stateDir });
125
+ }
126
+ fs.rmSync(runtimeDir, { recursive: true, force: true });
127
+ }
128
+
129
+ function normalizeCacheSelection(cache) {
130
+ const values = Array.isArray(cache) ? cache.filter(Boolean).map(String) : [cache].filter(Boolean).map(String);
131
+ if (values.includes("all")) return ["runtime", "bundles", "assistant"];
132
+ return values;
133
+ }
134
+
135
+ function pruneKnownEmptyDirs(productDir) {
136
+ for (const dir of [
137
+ path.join(productDir, ".testkit", "assistant", "command-results"),
138
+ path.join(productDir, ".testkit", "assistant"),
139
+ path.join(productDir, ".testkit", "_bundles"),
140
+ path.join(productDir, ".testkit", "_graphs"),
141
+ ]) {
142
+ pruneEmptyDir(dir);
143
+ }
144
+ }
145
+
146
+ function pruneEmptyDir(dir) {
147
+ if (!fs.existsSync(dir)) return;
148
+ try {
149
+ if (fs.readdirSync(dir).length === 0) fs.rmSync(dir, { recursive: true, force: true });
150
+ } catch {
151
+ // Best-effort cleanup only.
152
+ }
153
+ }
154
+
155
+ function relativeToProduct(productDir, filePath) {
156
+ return path.relative(productDir, filePath).split(path.sep).join("/");
157
+ }
158
+
159
+ function appendBoundedFileCleanupLines(lines, { productDir, targets, label, dryRun }) {
160
+ if (!targets || targets.length === 0) return;
161
+ const verb = dryRun ? "Would remove" : "Removed";
162
+ lines.push(`${verb} ${targets.length} ${label}${targets.length === 1 ? "" : "s"}.`);
163
+ for (const target of targets.slice(0, 10)) {
164
+ lines.push(` ${relativeToProduct(productDir, target.path)}`);
165
+ }
166
+ if (targets.length > 10) {
167
+ lines.push(` ... ${targets.length - 10} more ${label}${targets.length - 10 === 1 ? "" : "s"}`);
168
+ }
169
+ }
170
+
171
+ function collectRunCleanupPreview(productDir) {
172
+ const summary = {
173
+ cleaned: [],
174
+ skippedActive: [],
175
+ };
176
+ for (const manifest of listRunManifests(productDir)) {
177
+ if (isPidRunning(manifest.pid)) {
178
+ summary.skippedActive.push(manifest);
179
+ } else {
180
+ summary.cleaned.push(manifest);
181
+ }
182
+ }
183
+ return summary;
184
+ }
@@ -4,7 +4,7 @@ import {
4
4
  suiteSelectionType,
5
5
  } from "./suite-selection.mjs";
6
6
 
7
- const TYPE_ORDER = ["dal", "integration", "e2e", "scenario", "load"];
7
+ const TYPE_ORDER = ["ui", "e2e", "scenario", "integration", "dal", "load"];
8
8
 
9
9
  export function taskNeedsLocalRuntime(task) {
10
10
  return task.type !== "dal";
@@ -263,7 +263,6 @@ function finalizeSuite(suite) {
263
263
  return {
264
264
  name: suite.name,
265
265
  type: suite.displayType,
266
- framework: formatFrameworkForArtifact(suite.framework),
267
266
  failed: suite.failedFiles.length > 0,
268
267
  fileCount: suite.fileCount,
269
268
  completedFileCount: suite.completedFileCount,
@@ -285,11 +284,6 @@ function normalizePathSeparators(filePath) {
285
284
  return filePath.split(path.sep).join("/");
286
285
  }
287
286
 
288
- function formatFrameworkForArtifact(framework) {
289
- if (framework === "k6") return "default";
290
- return framework;
291
- }
292
-
293
287
  function normalizeOutcomeStatus(outcome) {
294
288
  if (outcome?.status === "not_run") return "not_run";
295
289
  if (outcome?.status === "skipped") return "skipped";