@elench/testkit 0.1.96 → 0.1.98
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/lib/app/browser-bridge.mjs +1 -1
- package/lib/cli/assistant/app.mjs +49 -12
- package/lib/cli/assistant/composer.mjs +19 -1
- package/lib/cli/assistant/context-pack.mjs +9 -8
- package/lib/cli/assistant/interactive.mjs +1 -1
- package/lib/cli/assistant/model-discovery.mjs +243 -0
- package/lib/cli/assistant/prompt-builder.mjs +2 -5
- package/lib/cli/{agents → assistant}/providers/claude.mjs +41 -3
- package/lib/cli/{agents → assistant}/providers/codex.mjs +33 -14
- package/lib/cli/{agents → assistant/providers}/index.mjs +3 -3
- package/lib/cli/{agents → assistant}/providers/shared.mjs +6 -2
- package/lib/cli/assistant/session.mjs +31 -6
- package/lib/cli/assistant/slash-commands.mjs +30 -3
- package/lib/cli/assistant/state.mjs +237 -71
- package/lib/cli/assistant/tool-registry.mjs +325 -39
- package/lib/cli/assistant/view-model.mjs +1 -1
- package/lib/cli/commands/assistant.mjs +4 -3
- package/lib/cli/commands/browser/serve.mjs +5 -23
- package/lib/cli/commands/cleanup.mjs +8 -2
- package/lib/cli/commands/db/snapshot/capture.mjs +8 -4
- package/lib/cli/commands/destroy.mjs +8 -2
- package/lib/cli/commands/discover.mjs +5 -27
- package/lib/cli/commands/doctor.mjs +5 -5
- package/lib/cli/commands/flags.mjs +61 -0
- package/lib/cli/commands/run.mjs +10 -2
- package/lib/cli/commands/status.mjs +10 -2
- package/lib/cli/commands/typecheck.mjs +5 -5
- package/lib/cli/{tui/inspect-app.mjs → components/blocks/run-tree.mjs} +29 -54
- package/lib/cli/{tui → components/primitives}/filter-bar.mjs +1 -1
- package/lib/cli/{presentation → components/primitives}/summary-box.mjs +1 -1
- package/lib/cli/config.mjs +63 -0
- package/lib/cli/operations/browser/serve/operation.mjs +23 -0
- package/lib/cli/operations/cleanup/operation.mjs +8 -0
- package/lib/cli/{db.mjs → operations/db/snapshot/capture/operation.mjs} +15 -9
- package/lib/cli/operations/destroy/operation.mjs +12 -0
- package/lib/cli/operations/discover/operation.mjs +32 -0
- package/lib/cli/operations/doctor/operation.mjs +5 -0
- package/lib/cli/operations/run/operation.mjs +129 -0
- package/lib/cli/operations/status/operation.mjs +7 -0
- package/lib/cli/operations/typecheck/operation.mjs +5 -0
- package/lib/cli/renderers/browser-serve/text.mjs +6 -0
- package/lib/cli/renderers/cleanup/text.mjs +3 -0
- package/lib/cli/renderers/db-snapshot-capture/text.mjs +3 -0
- package/lib/cli/renderers/destroy/text.mjs +3 -0
- package/lib/cli/{presentation/discovery-reporter.mjs → renderers/discover/report.mjs} +3 -3
- package/lib/cli/renderers/discover/text.mjs +7 -0
- package/lib/cli/renderers/doctor/text.mjs +7 -0
- package/lib/cli/{presentation/failure-presentation.mjs → renderers/run/failure.mjs} +6 -6
- package/lib/cli/renderers/run/interactive.mjs +119 -0
- package/lib/cli/{presentation/run-reporter.mjs → renderers/run/text-reporter.mjs} +5 -5
- package/lib/cli/renderers/status/text.mjs +7 -0
- package/lib/cli/renderers/typecheck/text.mjs +7 -0
- package/lib/cli/{tui/inspect-model.mjs → state/run/model.mjs} +11 -26
- package/lib/cli/{tui/inspect-state.mjs → state/run/state.mjs} +11 -18
- package/lib/cli/{tui → state/tree}/fuzzy-match.mjs +1 -1
- package/lib/cli/terminal/capabilities.mjs +33 -0
- package/lib/database/index.mjs +9 -21
- package/lib/database/template-steps.mjs +3 -3
- package/lib/{cli/viewer.mjs → results/artifacts.mjs} +1 -1
- package/lib/{cli/context-resources.mjs → results/context.mjs} +1 -1
- package/lib/runner/maintenance.mjs +25 -14
- package/lib/runner/readiness.mjs +5 -4
- package/lib/runner/runtime-preparation.mjs +36 -0
- package/lib/runner/state-io.mjs +10 -4
- package/lib/runner/template.mjs +24 -3
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +5 -5
- package/lib/cli/assistant/command-plan.mjs +0 -227
- package/lib/cli/command-helpers.mjs +0 -191
- package/lib/cli/presentation/tree-reporter.mjs +0 -96
- package/lib/cli/tui/inspect-artifact-adapter.mjs +0 -3
- package/lib/cli/tui/inspect-live-adapter.mjs +0 -15
- /package/lib/cli/{presentation/events-reporter.mjs → renderers/run/events.mjs} +0 -0
- /package/lib/cli/{presentation → terminal}/colors.mjs +0 -0
- /package/lib/cli/{presentation/terminal-layout.mjs → terminal/layout.mjs} +0 -0
- /package/lib/{cli/presentation → results}/code-frames.mjs +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as runner from "../../../runner/index.mjs";
|
|
2
|
+
import { loadManagedConfigs } from "../../../app/configs.mjs";
|
|
3
|
+
|
|
4
|
+
export async function executeStatusOperation(flags = {}) {
|
|
5
|
+
const { configs } = await loadManagedConfigs({ dir: flags.dir, service: flags.service });
|
|
6
|
+
return configs.map((config) => runner.showStatus(config));
|
|
7
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { formatDuration } from "
|
|
2
|
-
import { formatSelectionTypeLabel } from "
|
|
1
|
+
import { formatDuration } from "../../../runner/formatting.mjs";
|
|
2
|
+
import { formatSelectionTypeLabel } from "../../../discovery/index.mjs";
|
|
3
3
|
import {
|
|
4
4
|
bold,
|
|
5
5
|
colorDiagnosticSeverity,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
colorTypeBadge,
|
|
9
9
|
muted,
|
|
10
10
|
statusLabel,
|
|
11
|
-
} from "
|
|
11
|
+
} from "../../terminal/colors.mjs";
|
|
12
12
|
|
|
13
13
|
const TYPE_ORDER = ["int", "e2e", "scenario", "dal", "load", "pw"];
|
|
14
14
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { buildDiscoveryReportLines } from "./report.mjs";
|
|
2
|
+
|
|
3
|
+
export function renderDiscoverResult(result, options = {}) {
|
|
4
|
+
const lines = buildDiscoveryReportLines(result, { outputMode: options.outputMode || "compact" });
|
|
5
|
+
if (result.outputLabel) lines.push(`Wrote ${result.outputLabel}`);
|
|
6
|
+
return lines;
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function renderDoctorResult(result) {
|
|
2
|
+
const lines = [`testkit doctor ${result.ok ? "passed" : "failed"} for ${result.productDir}`];
|
|
3
|
+
for (const check of result.checks || []) {
|
|
4
|
+
lines.push(`${check.level.toUpperCase()} ${check.code} ${check.message}`);
|
|
5
|
+
}
|
|
6
|
+
return lines;
|
|
7
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { buildFailurePresentation } from "
|
|
2
|
-
import { renderIndentedBlock } from "
|
|
1
|
+
import { buildFailurePresentation } from "../../../runner/formatting.mjs";
|
|
2
|
+
import { renderIndentedBlock } from "../../terminal/layout.mjs";
|
|
3
3
|
|
|
4
4
|
export function renderFailureBlock(task, outcome, { width, regressionCatalog } = {}) {
|
|
5
|
-
const
|
|
5
|
+
const failureView = buildFailurePresentation(
|
|
6
6
|
{
|
|
7
7
|
service: task.serviceName,
|
|
8
8
|
type: normalizeRegressionType(task),
|
|
@@ -15,10 +15,10 @@ export function renderFailureBlock(task, outcome, { width, regressionCatalog } =
|
|
|
15
15
|
);
|
|
16
16
|
|
|
17
17
|
const lines = [];
|
|
18
|
-
if (
|
|
19
|
-
lines.push(...renderIndentedBlock(
|
|
18
|
+
if (failureView.primary) {
|
|
19
|
+
lines.push(...renderIndentedBlock(failureView.primary, { width, indent: " " }));
|
|
20
20
|
}
|
|
21
|
-
for (const detail of
|
|
21
|
+
for (const detail of failureView.details) {
|
|
22
22
|
lines.push(...renderIndentedBlock(detail, { width, indent: " " }));
|
|
23
23
|
}
|
|
24
24
|
return lines;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import React, { createElement } from "react";
|
|
2
|
+
import { render } from "ink";
|
|
3
|
+
import { createRunState } from "../../state/run/state.mjs";
|
|
4
|
+
import { RunTreeView } from "../../components/blocks/run-tree.mjs";
|
|
5
|
+
import { suiteSelectionType } from "../../../runner/suite-selection.mjs";
|
|
6
|
+
|
|
7
|
+
export function createRunSession({
|
|
8
|
+
productDir,
|
|
9
|
+
dataSource = "live",
|
|
10
|
+
stderr = process.stderr,
|
|
11
|
+
onSnapshot,
|
|
12
|
+
config = {},
|
|
13
|
+
} = {}) {
|
|
14
|
+
const runState = createRunState({
|
|
15
|
+
dataSource,
|
|
16
|
+
autoCollapsePassedTreeBranches: config.autoCollapsePassedTreeBranches,
|
|
17
|
+
});
|
|
18
|
+
if (typeof onSnapshot === "function") {
|
|
19
|
+
runState.subscribe(() => {
|
|
20
|
+
onSnapshot(runState.getSnapshot());
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const reporter = {
|
|
25
|
+
outputMode: "compact",
|
|
26
|
+
|
|
27
|
+
setServicePlans(plans) {
|
|
28
|
+
runState.initFromPlans(plans);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
setTotalFileCount(count) {
|
|
32
|
+
runState.setTotalFileCount(count);
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
setRegressionCatalog(document) {
|
|
36
|
+
runState.setRegressionCatalog(document);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
serviceSkipped(config, reason) {
|
|
40
|
+
runState.markServiceSkipped(config.name, reason);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
plannedSkip(entry) {
|
|
44
|
+
runState.markPlannedSkip(entry);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
taskStarted(task) {
|
|
48
|
+
const suiteKey = `${task.displayType || suiteSelectionType(task.type, task.framework)}:${task.suiteName}`;
|
|
49
|
+
runState.markFileRunning(task.serviceName, suiteKey, task.file);
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
taskFinished(task, outcome) {
|
|
53
|
+
runState.markFileFinished(task, outcome);
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
runtimeError(task, message) {
|
|
57
|
+
runState.markRuntimeError(task, message);
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
setupOperationFinished() {},
|
|
61
|
+
|
|
62
|
+
phaseStarted(label) {
|
|
63
|
+
runState.setPhase(label);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
toolchainResolved() {},
|
|
67
|
+
localServiceStarting() {},
|
|
68
|
+
writeLine() {},
|
|
69
|
+
writeDebugLine() {},
|
|
70
|
+
telemetry() {},
|
|
71
|
+
|
|
72
|
+
runSummary(results, durationMs, regressionReport) {
|
|
73
|
+
runState.finish(results, durationMs, regressionReport);
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
error(message) {
|
|
77
|
+
stderr.write(`${message}\n`);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
productDir,
|
|
83
|
+
runState,
|
|
84
|
+
reporter,
|
|
85
|
+
getSnapshot() {
|
|
86
|
+
return runState.getSnapshot();
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function renderRunSessionView(
|
|
92
|
+
session,
|
|
93
|
+
{
|
|
94
|
+
stdout = process.stdout,
|
|
95
|
+
stderr = process.stderr,
|
|
96
|
+
interactive = true,
|
|
97
|
+
onRequestClose,
|
|
98
|
+
} = {}
|
|
99
|
+
) {
|
|
100
|
+
const app = render(
|
|
101
|
+
createElement(RunTreeView, {
|
|
102
|
+
runState: session.runState,
|
|
103
|
+
stdout,
|
|
104
|
+
onRequestClose: close,
|
|
105
|
+
interactive,
|
|
106
|
+
}),
|
|
107
|
+
{ stdout, stderr, exitOnCtrlC: false }
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
finalize: app.waitUntilExit(),
|
|
112
|
+
close,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
function close() {
|
|
116
|
+
app.unmount();
|
|
117
|
+
if (typeof onRequestClose === "function") onRequestClose();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
buildRunSummaryData,
|
|
4
4
|
buildDebugRunSummaryLines,
|
|
5
5
|
formatDuration,
|
|
6
|
-
} from "
|
|
7
|
-
import { boldRed, colorSectionLine, dim, statusLabel } from "
|
|
8
|
-
import { renderFailureBlock } from "./failure
|
|
9
|
-
import { renderSummaryBox } from "
|
|
10
|
-
import { getTerminalWidth } from "
|
|
6
|
+
} from "../../../runner/formatting.mjs";
|
|
7
|
+
import { boldRed, colorSectionLine, dim, statusLabel } from "../../terminal/colors.mjs";
|
|
8
|
+
import { renderFailureBlock } from "./failure.mjs";
|
|
9
|
+
import { renderSummaryBox } from "../../components/primitives/summary-box.mjs";
|
|
10
|
+
import { getTerminalWidth } from "../../terminal/layout.mjs";
|
|
11
11
|
|
|
12
12
|
export function createRunReporter({ outputMode = "compact", stdout = process.stdout, stderr = process.stderr } = {}) {
|
|
13
13
|
const mode = outputMode || "compact";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function renderTypecheckResult(result) {
|
|
2
|
+
const lines = [`Typechecked ${result.results.length} testkit program(s) in ${result.productDir}`];
|
|
3
|
+
for (const entry of result.results) {
|
|
4
|
+
lines.push(`PASS ${entry.label} ${entry.tsconfigPath}`);
|
|
5
|
+
}
|
|
6
|
+
return lines;
|
|
7
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { fileDisplayName } from "
|
|
2
|
-
import { suiteSelectionType } from "
|
|
3
|
-
import { formatDuration } from "
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
export const INSPECT_PANES = ["detail", "artifacts", "logs", "setup"];
|
|
1
|
+
import { fileDisplayName } from "../../../discovery/index.mjs";
|
|
2
|
+
import { suiteSelectionType } from "../../../runner/suite-selection.mjs";
|
|
3
|
+
import { formatDuration } from "../../../runner/formatting.mjs";
|
|
4
|
+
import { matchRunTreeEntry } from "../tree/fuzzy-match.mjs";
|
|
7
5
|
|
|
8
6
|
export function buildSummaryRows({
|
|
9
7
|
result,
|
|
@@ -41,9 +39,10 @@ export function buildSummaryRows({
|
|
|
41
39
|
return rows;
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
export function
|
|
42
|
+
export function createEmptyRunModel(dataSource = "live", options = {}) {
|
|
45
43
|
return {
|
|
46
44
|
dataSource,
|
|
45
|
+
autoCollapsePassedTreeBranches: options.autoCollapsePassedTreeBranches !== false,
|
|
47
46
|
services: new Map(),
|
|
48
47
|
summaryData: null,
|
|
49
48
|
regressionCatalog: null,
|
|
@@ -57,11 +56,10 @@ export function createEmptyInspectModel(dataSource = "live") {
|
|
|
57
56
|
filterMatches: new Map(),
|
|
58
57
|
collapsedOverrides: new Map(),
|
|
59
58
|
selectedEntryId: null,
|
|
60
|
-
paneMode: "detail",
|
|
61
59
|
};
|
|
62
60
|
}
|
|
63
61
|
|
|
64
|
-
export function
|
|
62
|
+
export function resetRunModel(model, dataSource = model.dataSource) {
|
|
65
63
|
model.dataSource = dataSource;
|
|
66
64
|
model.services = new Map();
|
|
67
65
|
model.summaryData = null;
|
|
@@ -76,7 +74,6 @@ export function resetInspectModel(model, dataSource = model.dataSource) {
|
|
|
76
74
|
model.filterMatches = new Map();
|
|
77
75
|
model.collapsedOverrides = new Map();
|
|
78
76
|
model.selectedEntryId = null;
|
|
79
|
-
model.paneMode = "detail";
|
|
80
77
|
}
|
|
81
78
|
|
|
82
79
|
export function initModelFromPlans(model, servicePlans) {
|
|
@@ -124,7 +121,7 @@ export function initModelFromPlans(model, servicePlans) {
|
|
|
124
121
|
}
|
|
125
122
|
|
|
126
123
|
export function applyArtifactToModel(model, artifact) {
|
|
127
|
-
|
|
124
|
+
resetRunModel(model, artifact?.run?.status === "running" ? "live" : "artifact");
|
|
128
125
|
model.runArtifact = artifact;
|
|
129
126
|
model.finished = artifact?.run?.status !== "running";
|
|
130
127
|
model.phase = model.finished ? "run complete" : "live artifact";
|
|
@@ -287,24 +284,13 @@ export function updateFilter(model, query) {
|
|
|
287
284
|
const normalizedQuery = model.filterQuery.trim();
|
|
288
285
|
if (!normalizedQuery) return;
|
|
289
286
|
for (const entry of collectAllEntries(model)) {
|
|
290
|
-
const match =
|
|
287
|
+
const match = matchRunTreeEntry(normalizedQuery, entry);
|
|
291
288
|
if (match.matched) {
|
|
292
289
|
model.filterMatches.set(entry.id, match);
|
|
293
290
|
}
|
|
294
291
|
}
|
|
295
292
|
}
|
|
296
293
|
|
|
297
|
-
export function cyclePane(model) {
|
|
298
|
-
const index = INSPECT_PANES.indexOf(model.paneMode);
|
|
299
|
-
model.paneMode = INSPECT_PANES[(index + 1) % INSPECT_PANES.length];
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export function setPane(model, paneMode) {
|
|
303
|
-
if (INSPECT_PANES.includes(paneMode)) {
|
|
304
|
-
model.paneMode = paneMode;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
294
|
export function toggleCollapsed(model, entryId) {
|
|
309
295
|
const entry = getEntryById(model, entryId);
|
|
310
296
|
if (!entry || (entry.kind !== "type" && entry.kind !== "suite")) return;
|
|
@@ -350,7 +336,6 @@ export function buildSnapshot(model) {
|
|
|
350
336
|
finished: model.finished,
|
|
351
337
|
summaryData: model.summaryData,
|
|
352
338
|
regressionCatalog: model.regressionCatalog,
|
|
353
|
-
paneMode: model.paneMode,
|
|
354
339
|
filter: {
|
|
355
340
|
active: model.filterActive,
|
|
356
341
|
query: model.filterQuery,
|
|
@@ -413,7 +398,7 @@ function buildNestedServices(model) {
|
|
|
413
398
|
const suites = [...typeNode.suites.values()].map((suite) => {
|
|
414
399
|
const files = [...suite.files.values()];
|
|
415
400
|
const summary = summarizeFiles(files);
|
|
416
|
-
const autoCollapsed = summary.total > 0 && (summary.passed === summary.total || summary.skipped === summary.total);
|
|
401
|
+
const autoCollapsed = model.autoCollapsePassedTreeBranches && summary.total > 0 && (summary.passed === summary.total || summary.skipped === summary.total);
|
|
417
402
|
return {
|
|
418
403
|
id: suite.id,
|
|
419
404
|
kind: "suite",
|
|
@@ -430,7 +415,7 @@ function buildNestedServices(model) {
|
|
|
430
415
|
};
|
|
431
416
|
});
|
|
432
417
|
const summary = summarizeNestedEntries(suites.map((suite) => suite.summary));
|
|
433
|
-
const autoCollapsed = suites.length > 0 && suites.every((suite) => suite.collapsed);
|
|
418
|
+
const autoCollapsed = model.autoCollapsePassedTreeBranches && suites.length > 0 && suites.every((suite) => suite.collapsed);
|
|
434
419
|
return {
|
|
435
420
|
id: typeNode.id,
|
|
436
421
|
kind: "type",
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyArtifactToModel,
|
|
3
3
|
buildSnapshot,
|
|
4
|
-
|
|
5
|
-
cyclePane,
|
|
4
|
+
createEmptyRunModel,
|
|
6
5
|
findEntryIdForFile,
|
|
7
6
|
findEntryIdForService,
|
|
8
7
|
finishModel,
|
|
@@ -12,18 +11,17 @@ import {
|
|
|
12
11
|
markPlannedSkip,
|
|
13
12
|
markRuntimeError,
|
|
14
13
|
markServiceSkipped,
|
|
15
|
-
|
|
14
|
+
resetRunModel,
|
|
16
15
|
revealEntry,
|
|
17
|
-
setPane,
|
|
18
16
|
setPhase,
|
|
19
17
|
setRegressionCatalog,
|
|
20
18
|
setTotalFileCount,
|
|
21
19
|
toggleCollapsed,
|
|
22
20
|
updateFilter,
|
|
23
|
-
} from "./
|
|
21
|
+
} from "./model.mjs";
|
|
24
22
|
|
|
25
|
-
export function
|
|
26
|
-
const model =
|
|
23
|
+
export function createRunState({ dataSource = "live", autoCollapsePassedTreeBranches = true } = {}) {
|
|
24
|
+
const model = createEmptyRunModel(dataSource, { autoCollapsePassedTreeBranches });
|
|
27
25
|
let notice = null;
|
|
28
26
|
const listeners = new Set();
|
|
29
27
|
|
|
@@ -59,11 +57,16 @@ export function createInspectState({ dataSource = "live" } = {}) {
|
|
|
59
57
|
},
|
|
60
58
|
|
|
61
59
|
resetForLive() {
|
|
62
|
-
|
|
60
|
+
resetRunModel(model, "live");
|
|
63
61
|
notice = null;
|
|
64
62
|
notify();
|
|
65
63
|
},
|
|
66
64
|
|
|
65
|
+
setAutoCollapsePassedTreeBranches(enabled) {
|
|
66
|
+
model.autoCollapsePassedTreeBranches = Boolean(enabled);
|
|
67
|
+
notify();
|
|
68
|
+
},
|
|
69
|
+
|
|
67
70
|
setRegressionCatalog(document) {
|
|
68
71
|
setRegressionCatalog(model, document);
|
|
69
72
|
notify();
|
|
@@ -177,16 +180,6 @@ export function createInspectState({ dataSource = "live" } = {}) {
|
|
|
177
180
|
return true;
|
|
178
181
|
},
|
|
179
182
|
|
|
180
|
-
cyclePaneMode() {
|
|
181
|
-
cyclePane(model);
|
|
182
|
-
notify();
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
setPaneMode(paneMode) {
|
|
186
|
-
setPane(model, paneMode);
|
|
187
|
-
notify();
|
|
188
|
-
},
|
|
189
|
-
|
|
190
183
|
subscribe(callback) {
|
|
191
184
|
listeners.add(callback);
|
|
192
185
|
return () => listeners.delete(callback);
|
|
@@ -44,7 +44,7 @@ export function fuzzyMatch(query, candidate) {
|
|
|
44
44
|
return { matched: false, score: Number.NEGATIVE_INFINITY, positions: [] };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export function
|
|
47
|
+
export function matchRunTreeEntry(query, entry) {
|
|
48
48
|
const fields = buildEntryMatchFields(entry);
|
|
49
49
|
let best = null;
|
|
50
50
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function resolveTerminalCapabilities({
|
|
2
|
+
stdout = process.stdout,
|
|
3
|
+
stderr = process.stderr,
|
|
4
|
+
stdin = process.stdin,
|
|
5
|
+
env = process.env,
|
|
6
|
+
forceInteractive = false,
|
|
7
|
+
} = {}) {
|
|
8
|
+
const interactiveOutput = Boolean(stdout?.isTTY || forceInteractive);
|
|
9
|
+
const interactiveInput = Boolean(stdin?.isTTY);
|
|
10
|
+
return {
|
|
11
|
+
stdout,
|
|
12
|
+
stderr,
|
|
13
|
+
stdin,
|
|
14
|
+
env,
|
|
15
|
+
forceInteractive,
|
|
16
|
+
interactiveOutput,
|
|
17
|
+
interactiveInput,
|
|
18
|
+
colorEnabled: Boolean(interactiveOutput || env?.FORCE_COLOR),
|
|
19
|
+
width: stdout?.columns || process.stdout?.columns || 100,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveRunOutputMode({ requestedMode = "compact", json = false, debug = false } = {}) {
|
|
24
|
+
if (json) return "json";
|
|
25
|
+
if (debug) return "debug";
|
|
26
|
+
return requestedMode || "compact";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function selectRunRenderer(outputMode, capabilities) {
|
|
30
|
+
if (outputMode === "events") return "events";
|
|
31
|
+
if (outputMode === "compact" && capabilities?.interactiveOutput) return "tree";
|
|
32
|
+
return "text";
|
|
33
|
+
}
|
package/lib/database/index.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
visitDirs as visitDirsModel,
|
|
27
27
|
} from "./state.mjs";
|
|
28
28
|
import { captureTemplateSnapshot, runTemplateStage } from "./template-steps.mjs";
|
|
29
|
+
import { collectStateDirLines } from "../runner/state-io.mjs";
|
|
29
30
|
|
|
30
31
|
const LOCAL_IMAGE = "pgvector/pgvector:pg16";
|
|
31
32
|
const LOCAL_USER = "testkit";
|
|
@@ -154,22 +155,23 @@ export function isDatabaseStateDir(dir) {
|
|
|
154
155
|
return fs.existsSync(path.join(dir, "database_backend"));
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
export function
|
|
158
|
+
export function collectServiceDatabaseStatus(productDir, serviceName) {
|
|
158
159
|
const cacheDir = getLocalServiceCacheDir(productDir, serviceName);
|
|
159
160
|
const infraDir = getLocalInfraDir(productDir);
|
|
160
|
-
if (!fs.existsSync(cacheDir) && !fs.existsSync(infraDir)) return
|
|
161
|
+
if (!fs.existsSync(cacheDir) && !fs.existsSync(infraDir)) return [];
|
|
161
162
|
|
|
163
|
+
const lines = [];
|
|
162
164
|
if (fs.existsSync(cacheDir)) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
lines.push(" database-cache/");
|
|
166
|
+
lines.push(...collectStateDirLines(cacheDir, " "));
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
if (fs.existsSync(infraDir)) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
lines.push(" database-infra/");
|
|
171
|
+
lines.push(...collectStateDirLines(infraDir, " "));
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
return
|
|
174
|
+
return lines;
|
|
173
175
|
}
|
|
174
176
|
|
|
175
177
|
async function prepareLocalDatabase(config, options = {}) {
|
|
@@ -664,20 +666,6 @@ function visitDirs(root, visitor) {
|
|
|
664
666
|
return visitDirsModel(root, visitor);
|
|
665
667
|
}
|
|
666
668
|
|
|
667
|
-
function printStateDir(dir, indent) {
|
|
668
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
669
|
-
const filePath = path.join(dir, entry.name);
|
|
670
|
-
if (entry.isDirectory()) {
|
|
671
|
-
console.log(`${indent}${entry.name}/`);
|
|
672
|
-
printStateDir(filePath, `${indent} `);
|
|
673
|
-
continue;
|
|
674
|
-
}
|
|
675
|
-
const value =
|
|
676
|
-
entry.name === "password" ? "********" : fs.readFileSync(filePath, "utf8").trim();
|
|
677
|
-
console.log(`${indent}${entry.name}: ${value}`);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
669
|
async function withLock(lockPath, fn) {
|
|
682
670
|
const lockDir = `${lockPath}.dir`;
|
|
683
671
|
const timeoutMs = 60_000;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { execa } from "execa";
|
|
4
|
-
import {
|
|
4
|
+
import { buildTemplateExecutionEnv } from "../runner/template.mjs";
|
|
5
5
|
import {
|
|
6
6
|
collectConfiguredInputs,
|
|
7
7
|
runConfiguredSteps,
|
|
@@ -13,7 +13,7 @@ export async function runTemplateStage(config, stageName, databaseUrl, options =
|
|
|
13
13
|
if (steps.length === 0) return;
|
|
14
14
|
|
|
15
15
|
const env = {
|
|
16
|
-
...
|
|
16
|
+
...buildTemplateExecutionEnv(config, {}, process.env),
|
|
17
17
|
DATABASE_URL: databaseUrl,
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -54,7 +54,7 @@ export async function captureTemplateSnapshot(config, outputPath, databaseUrl, o
|
|
|
54
54
|
{
|
|
55
55
|
cwd: config.productDir,
|
|
56
56
|
env: {
|
|
57
|
-
...
|
|
57
|
+
...buildTemplateExecutionEnv(config, {}, process.env),
|
|
58
58
|
DATABASE_URL: templateDbUrl,
|
|
59
59
|
},
|
|
60
60
|
stdout: "pipe",
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
findFailureLocation,
|
|
7
7
|
formatLocation,
|
|
8
8
|
renderCodeFrame,
|
|
9
|
-
} from "./
|
|
9
|
+
} from "./code-frames.mjs";
|
|
10
10
|
|
|
11
11
|
export function loadLatestRunArtifact(productDir) {
|
|
12
12
|
const artifactPath = path.join(productDir, ".testkit", "results", "latest.json");
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import {
|
|
3
3
|
cleanupOrphanedLocalInfrastructure,
|
|
4
|
+
collectServiceDatabaseStatus,
|
|
4
5
|
destroyRuntimeDatabase,
|
|
5
6
|
destroyServiceDatabaseCache,
|
|
6
7
|
isDatabaseStateDir,
|
|
7
|
-
showServiceDatabaseStatus,
|
|
8
8
|
} from "../database/index.mjs";
|
|
9
9
|
import { cleanupRuns, formatRunSummary } from "./lifecycle.mjs";
|
|
10
|
-
import {
|
|
10
|
+
import { buildRunStatusLines } from "./readiness.mjs";
|
|
11
11
|
import { findGraphDirsForService, findRuntimeStateDirs } from "./state.mjs";
|
|
12
|
-
import {
|
|
12
|
+
import { collectStateDirLines } from "./state-io.mjs";
|
|
13
13
|
|
|
14
14
|
export async function destroy(config) {
|
|
15
15
|
await cleanupRuns(config.productDir, { includeActive: true });
|
|
@@ -35,38 +35,49 @@ export async function destroy(config) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export function showStatus(config) {
|
|
38
|
-
|
|
38
|
+
const lines = [...buildRunStatusLines(config.productDir)];
|
|
39
39
|
const graphDirs = findGraphDirsForService(config.productDir, config.name);
|
|
40
40
|
const hasDirectState = fs.existsSync(config.stateDir);
|
|
41
41
|
const hasGraphState = graphDirs.length > 0;
|
|
42
42
|
|
|
43
43
|
if (!hasDirectState && !hasGraphState) {
|
|
44
|
-
|
|
44
|
+
lines.push("No state — run tests first.");
|
|
45
45
|
} else {
|
|
46
46
|
if (hasDirectState) {
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
lines.push(" service-state/");
|
|
48
|
+
lines.push(...collectStateDirLines(config.stateDir, " "));
|
|
49
49
|
}
|
|
50
50
|
for (const graphDir of graphDirs) {
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
lines.push(` graph-state/${graphDir.split("/").at(-1)}/`);
|
|
52
|
+
lines.push(...collectStateDirLines(graphDir, " "));
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
lines.push(...collectServiceDatabaseStatus(config.productDir, config.name));
|
|
57
|
+
return {
|
|
58
|
+
name: config.name,
|
|
59
|
+
lines,
|
|
60
|
+
};
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
export async function cleanup(productDir) {
|
|
60
64
|
const summary = await cleanupRuns(productDir, { includeActive: false });
|
|
61
65
|
if (summary.cleaned.length === 0 && summary.skippedActive.length === 0) {
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
return {
|
|
67
|
+
...summary,
|
|
68
|
+
lines: ["No stale runs to clean."],
|
|
69
|
+
};
|
|
64
70
|
}
|
|
65
71
|
|
|
72
|
+
const lines = [];
|
|
66
73
|
for (const manifest of summary.cleaned) {
|
|
67
|
-
|
|
74
|
+
lines.push(`Cleaned stale run ${formatRunSummary(manifest)}`);
|
|
68
75
|
}
|
|
69
76
|
for (const manifest of summary.skippedActive) {
|
|
70
|
-
|
|
77
|
+
lines.push(`Active run still present: ${formatRunSummary(manifest)}`);
|
|
71
78
|
}
|
|
79
|
+
return {
|
|
80
|
+
...summary,
|
|
81
|
+
lines,
|
|
82
|
+
};
|
|
72
83
|
}
|