@elench/testkit 0.1.108 → 0.1.109

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 (54) 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/command-observer.mjs +2 -1
  7. package/lib/cli/assistant/command-results.mjs +2 -1
  8. package/lib/cli/assistant/context-pack.mjs +2 -2
  9. package/lib/cli/assistant/prompt-builder.mjs +2 -2
  10. package/lib/cli/command-flags.mjs +2 -1
  11. package/lib/cli/commands/cleanup.mjs +13 -2
  12. package/lib/cli/commands/discover.mjs +2 -1
  13. package/lib/cli/commands/run.mjs +3 -2
  14. package/lib/cli/entrypoint.mjs +3 -1
  15. package/lib/cli/operations/cleanup/operation.mjs +6 -1
  16. package/lib/cli/operations/status/operation.mjs +2 -2
  17. package/lib/cli/renderers/discover/report.mjs +6 -8
  18. package/lib/cli/renderers/run/failure.mjs +1 -1
  19. package/lib/cli/renderers/run/text-reporter.mjs +1 -1
  20. package/lib/cli/renderers/status/text.mjs +101 -1
  21. package/lib/config/discovery.mjs +10 -1
  22. package/lib/config-api/index.mjs +2 -2
  23. package/lib/config-api/next-runtime-tsconfig.mjs +2 -1
  24. package/lib/coverage/graph-builder.mjs +2 -4
  25. package/lib/coverage/routing.mjs +1 -1
  26. package/lib/coverage/shared.mjs +1 -2
  27. package/lib/discovery/index.d.ts +5 -8
  28. package/lib/discovery/index.mjs +15 -24
  29. package/lib/domain/test-types.mjs +44 -0
  30. package/lib/history/index.d.ts +3 -4
  31. package/lib/history/index.mjs +6 -14
  32. package/lib/runner/formatting.mjs +2 -3
  33. package/lib/runner/maintenance.mjs +136 -35
  34. package/lib/runner/planning.mjs +1 -1
  35. package/lib/runner/results.mjs +0 -6
  36. package/lib/runner/status-model.mjs +520 -0
  37. package/lib/runner/suite-selection.mjs +20 -11
  38. package/lib/runner/template-steps.mjs +2 -2
  39. package/lib/runner/template.mjs +4 -0
  40. package/lib/ui/index.d.ts +1 -0
  41. package/lib/ui/index.mjs +1 -0
  42. package/lib/vitest/index.mjs +2 -1
  43. package/node_modules/@elench/next-analysis/package.json +1 -1
  44. package/node_modules/@elench/testkit-bridge/dist/index.js +9 -11
  45. package/node_modules/@elench/testkit-bridge/dist/index.js.map +1 -1
  46. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  47. package/node_modules/@elench/testkit-protocol/dist/index.d.ts +1 -3
  48. package/node_modules/@elench/testkit-protocol/dist/index.d.ts.map +1 -1
  49. package/node_modules/@elench/testkit-protocol/dist/index.js +3 -6
  50. package/node_modules/@elench/testkit-protocol/dist/index.js.map +1 -1
  51. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  52. package/node_modules/@elench/ts-analysis/dist/requests.js +1 -1
  53. package/node_modules/@elench/ts-analysis/package.json +1 -1
  54. package/package.json +9 -9
@@ -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";