@elench/testkit 0.1.97 → 0.1.99
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/README.md +8 -8
- package/lib/app/browser-bridge.mjs +1 -1
- package/lib/cli/assistant/actions.mjs +333 -0
- package/lib/cli/assistant/app.mjs +25 -1
- package/lib/cli/assistant/command-observer.mjs +110 -0
- package/lib/cli/assistant/command-results.mjs +167 -0
- package/lib/cli/assistant/composer.mjs +1 -1
- package/lib/cli/assistant/context-pack.mjs +73 -6
- package/lib/cli/assistant/interactive.mjs +1 -1
- package/lib/cli/assistant/prompt-builder.mjs +15 -8
- package/lib/cli/{agents → assistant}/providers/claude.mjs +2 -3
- package/lib/cli/{agents → assistant}/providers/codex.mjs +2 -6
- package/lib/cli/{agents → assistant/providers}/index.mjs +5 -5
- package/lib/cli/assistant/session.mjs +36 -94
- package/lib/cli/assistant/slash-commands.mjs +22 -1
- package/lib/cli/assistant/state.mjs +187 -100
- package/lib/cli/assistant/view-model.mjs +1 -1
- package/lib/cli/command-flags.mjs +61 -0
- 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 +13 -32
- package/lib/cli/commands/doctor.mjs +17 -14
- package/lib/cli/commands/run.mjs +14 -3
- package/lib/cli/commands/status.mjs +14 -3
- package/lib/cli/commands/typecheck.mjs +12 -9
- 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/entrypoint.mjs +14 -5
- 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/{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/state-io.mjs +10 -4
- 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 +7 -8
- package/lib/cli/assistant/protocol.mjs +0 -67
- package/lib/cli/assistant/tool-registry.mjs +0 -318
- 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/{agents → assistant}/providers/shared.mjs +0 -0
- /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
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
1
|
import { Command, Flags } from "@oclif/core";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { sharedFlags } from "../command-helpers.mjs";
|
|
2
|
+
import { executeDiscoverOperation } from "../operations/discover/operation.mjs";
|
|
3
|
+
import { renderDiscoverResult } from "../renderers/discover/text.mjs";
|
|
4
|
+
import { sharedFlags } from "../command-flags.mjs";
|
|
5
|
+
import { withAssistantCommandResult } from "../assistant/command-results.mjs";
|
|
9
6
|
|
|
10
7
|
export default class DiscoverCommand extends Command {
|
|
11
8
|
static summary = "Discover managed tests and report their metadata";
|
|
@@ -48,33 +45,17 @@ export default class DiscoverCommand extends Command {
|
|
|
48
45
|
};
|
|
49
46
|
|
|
50
47
|
async run() {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const result = await discoverTests({
|
|
55
|
-
dir: productDir,
|
|
56
|
-
service: flags.service || null,
|
|
57
|
-
type: flags.type || [],
|
|
58
|
-
suite: flags.suite || [],
|
|
59
|
-
file: fileNames,
|
|
60
|
-
runnableOnly: flags["runnable-only"],
|
|
61
|
-
diagnostics: flags.strict ? "error" : "report",
|
|
62
|
-
});
|
|
63
|
-
let outputLabel = null;
|
|
64
|
-
if (flags.output) {
|
|
65
|
-
const outputPath = path.resolve(productDir, flags.output);
|
|
66
|
-
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
67
|
-
fs.writeFileSync(outputPath, `${JSON.stringify(result, null, 2)}\n`);
|
|
68
|
-
outputLabel = path.relative(productDir, outputPath) || path.basename(outputPath);
|
|
69
|
-
}
|
|
48
|
+
return withAssistantCommandResult("discover", async () => {
|
|
49
|
+
const { flags } = await this.parse(DiscoverCommand);
|
|
50
|
+
const result = await executeDiscoverOperation(flags, process.cwd());
|
|
70
51
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
52
|
+
if (!this.jsonEnabled()) {
|
|
53
|
+
for (const line of renderDiscoverResult(result, { outputMode: flags["output-mode"] })) {
|
|
54
|
+
this.log(line);
|
|
55
|
+
}
|
|
74
56
|
}
|
|
75
|
-
if (outputLabel) this.log(`Wrote ${outputLabel}`);
|
|
76
|
-
}
|
|
77
57
|
|
|
78
|
-
|
|
58
|
+
return result;
|
|
59
|
+
});
|
|
79
60
|
}
|
|
80
61
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Command, Flags } from "@oclif/core";
|
|
2
|
-
import {
|
|
2
|
+
import { executeDoctorOperation } from "../operations/doctor/operation.mjs";
|
|
3
|
+
import { renderDoctorResult } from "../renderers/doctor/text.mjs";
|
|
4
|
+
import { withAssistantCommandResult } from "../assistant/command-results.mjs";
|
|
3
5
|
|
|
4
6
|
export default class DoctorCommand extends Command {
|
|
5
7
|
static summary = "Run built-in config, discovery, and hygiene checks";
|
|
@@ -18,22 +20,23 @@ export default class DoctorCommand extends Command {
|
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
async run() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
return withAssistantCommandResult("doctor", async () => {
|
|
24
|
+
const { flags } = await this.parse(DoctorCommand);
|
|
25
|
+
const result = await executeDoctorOperation(flags);
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
if (!this.jsonEnabled()) {
|
|
28
|
+
for (const line of renderDoctorResult(result)) {
|
|
29
|
+
this.log(line);
|
|
30
|
+
}
|
|
28
31
|
}
|
|
29
|
-
}
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
if (!result.ok) {
|
|
34
|
+
const error = new Error("testkit doctor failed");
|
|
35
|
+
error.result = result;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
return result;
|
|
40
|
+
});
|
|
38
41
|
}
|
|
39
42
|
}
|
package/lib/cli/commands/run.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Args, Command } from "@oclif/core";
|
|
2
|
-
import {
|
|
2
|
+
import { runFlags } from "../command-flags.mjs";
|
|
3
|
+
import { buildRunRequest, executeRunRequest } from "../operations/run/operation.mjs";
|
|
4
|
+
import { resolveTerminalCapabilities } from "../terminal/capabilities.mjs";
|
|
5
|
+
import { withAssistantCommandResult } from "../assistant/command-results.mjs";
|
|
3
6
|
|
|
4
7
|
export default class RunCommand extends Command {
|
|
5
8
|
static summary = "Run test suites";
|
|
@@ -17,7 +20,15 @@ export default class RunCommand extends Command {
|
|
|
17
20
|
static flags = runFlags;
|
|
18
21
|
|
|
19
22
|
async run() {
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
return withAssistantCommandResult("run", async () => {
|
|
24
|
+
const { args, flags } = await this.parse(RunCommand);
|
|
25
|
+
const request = await buildRunRequest(flags, args.type || null, process.cwd(), process.cwd());
|
|
26
|
+
return executeRunRequest(request, {
|
|
27
|
+
outputMode: flags["output-mode"] || "compact",
|
|
28
|
+
json: this.jsonEnabled(),
|
|
29
|
+
debug: flags.debug,
|
|
30
|
+
terminal: resolveTerminalCapabilities(),
|
|
31
|
+
});
|
|
32
|
+
});
|
|
22
33
|
}
|
|
23
34
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Command } from "@oclif/core";
|
|
2
|
-
import {
|
|
2
|
+
import { sharedFlags } from "../command-flags.mjs";
|
|
3
|
+
import { executeStatusOperation } from "../operations/status/operation.mjs";
|
|
4
|
+
import { renderStatusResult } from "../renderers/status/text.mjs";
|
|
5
|
+
import { withAssistantCommandResult } from "../assistant/command-results.mjs";
|
|
3
6
|
|
|
4
7
|
export default class StatusCommand extends Command {
|
|
5
8
|
static summary = "Show local testkit state";
|
|
@@ -9,7 +12,15 @@ export default class StatusCommand extends Command {
|
|
|
9
12
|
static flags = sharedFlags;
|
|
10
13
|
|
|
11
14
|
async run() {
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
return withAssistantCommandResult("status", async () => {
|
|
16
|
+
const { flags } = await this.parse(StatusCommand);
|
|
17
|
+
const results = await executeStatusOperation(flags);
|
|
18
|
+
if (!this.jsonEnabled()) {
|
|
19
|
+
for (const result of results) {
|
|
20
|
+
for (const line of renderStatusResult(result)) this.log(line);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { ok: true, results };
|
|
24
|
+
});
|
|
14
25
|
}
|
|
15
26
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Command, Flags } from "@oclif/core";
|
|
2
|
-
import {
|
|
2
|
+
import { executeTypecheckOperation } from "../operations/typecheck/operation.mjs";
|
|
3
|
+
import { renderTypecheckResult } from "../renderers/typecheck/text.mjs";
|
|
4
|
+
import { withAssistantCommandResult } from "../assistant/command-results.mjs";
|
|
3
5
|
|
|
4
6
|
export default class TypecheckCommand extends Command {
|
|
5
7
|
static summary = "Typecheck testkit config, helpers, and suites";
|
|
@@ -13,16 +15,17 @@ export default class TypecheckCommand extends Command {
|
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
async run() {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
return withAssistantCommandResult("typecheck", async () => {
|
|
19
|
+
const { flags } = await this.parse(TypecheckCommand);
|
|
20
|
+
const result = await executeTypecheckOperation(flags);
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
if (!this.jsonEnabled()) {
|
|
23
|
+
for (const line of renderTypecheckResult(result)) {
|
|
24
|
+
this.log(line);
|
|
25
|
+
}
|
|
23
26
|
}
|
|
24
|
-
}
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
return result;
|
|
29
|
+
});
|
|
27
30
|
}
|
|
28
31
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { createElement, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { Box, Text, useAnimation, useApp, useInput } from "ink";
|
|
3
3
|
import figures from "figures";
|
|
4
|
-
import { formatDuration } from "
|
|
4
|
+
import { formatDuration } from "../../../runner/formatting.mjs";
|
|
5
5
|
import {
|
|
6
6
|
bold,
|
|
7
7
|
colorService,
|
|
@@ -10,61 +10,60 @@ import {
|
|
|
10
10
|
green,
|
|
11
11
|
red,
|
|
12
12
|
yellow,
|
|
13
|
-
} from "
|
|
14
|
-
import { renderSummaryBox } from "../
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { applyHighlight } from "./fuzzy-match.mjs";
|
|
18
|
-
import { FilterBar } from "./filter-bar.mjs";
|
|
13
|
+
} from "../../terminal/colors.mjs";
|
|
14
|
+
import { renderSummaryBox } from "../primitives/summary-box.mjs";
|
|
15
|
+
import { applyHighlight } from "../../state/tree/fuzzy-match.mjs";
|
|
16
|
+
import { FilterBar } from "../primitives/filter-bar.mjs";
|
|
19
17
|
|
|
20
18
|
const SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
|
21
19
|
|
|
22
|
-
export function
|
|
23
|
-
|
|
20
|
+
export function RunTreeView({
|
|
21
|
+
runState,
|
|
24
22
|
stdout,
|
|
25
|
-
productDir,
|
|
26
23
|
onRequestClose,
|
|
24
|
+
interactive = true,
|
|
27
25
|
} = {}) {
|
|
28
26
|
const { exit } = useApp();
|
|
29
|
-
const [snapshot, setSnapshot] = useState(() =>
|
|
27
|
+
const [snapshot, setSnapshot] = useState(() => runState.getSnapshot());
|
|
30
28
|
const { frame } = useAnimation({ interval: 80, isActive: !snapshot.finished });
|
|
31
29
|
const spinnerFrame = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
|
|
32
30
|
|
|
33
31
|
useEffect(() => {
|
|
34
|
-
const unsubscribe =
|
|
35
|
-
setSnapshot(
|
|
32
|
+
const unsubscribe = runState.subscribe(() => {
|
|
33
|
+
setSnapshot(runState.getSnapshot());
|
|
36
34
|
});
|
|
37
35
|
return unsubscribe;
|
|
38
|
-
}, [
|
|
36
|
+
}, [runState]);
|
|
39
37
|
|
|
40
38
|
useInput((input, key) => {
|
|
39
|
+
if (!interactive) return;
|
|
41
40
|
if (!snapshot.finished) {
|
|
42
41
|
if (input === "q") {
|
|
43
|
-
|
|
42
|
+
runState.setNotice("Run is still in progress. Wait for completion before closing.");
|
|
44
43
|
}
|
|
45
44
|
return;
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
if (snapshot.filter.active) {
|
|
49
48
|
if (key.escape) {
|
|
50
|
-
|
|
49
|
+
runState.deactivateFilter();
|
|
51
50
|
return;
|
|
52
51
|
}
|
|
53
52
|
if (key.return) return;
|
|
54
53
|
if (key.downArrow || input === "j") {
|
|
55
|
-
|
|
54
|
+
runState.moveCursorDown();
|
|
56
55
|
return;
|
|
57
56
|
}
|
|
58
57
|
if (key.upArrow || input === "k") {
|
|
59
|
-
|
|
58
|
+
runState.moveCursorUp();
|
|
60
59
|
return;
|
|
61
60
|
}
|
|
62
61
|
if (key.backspace || key.delete) {
|
|
63
|
-
|
|
62
|
+
runState.updateFilterQuery(snapshot.filter.query.slice(0, -1));
|
|
64
63
|
return;
|
|
65
64
|
}
|
|
66
65
|
if (isPrintableInput(input, key)) {
|
|
67
|
-
|
|
66
|
+
runState.updateFilterQuery(`${snapshot.filter.query}${input}`);
|
|
68
67
|
}
|
|
69
68
|
return;
|
|
70
69
|
}
|
|
@@ -74,43 +73,27 @@ export function InspectApp({
|
|
|
74
73
|
return;
|
|
75
74
|
}
|
|
76
75
|
if (input === "/") {
|
|
77
|
-
|
|
76
|
+
runState.activateFilter();
|
|
78
77
|
return;
|
|
79
78
|
}
|
|
80
79
|
if (key.downArrow || input === "j") {
|
|
81
|
-
|
|
80
|
+
runState.moveCursorDown();
|
|
82
81
|
return;
|
|
83
82
|
}
|
|
84
83
|
if (key.upArrow || input === "k") {
|
|
85
|
-
|
|
84
|
+
runState.moveCursorUp();
|
|
86
85
|
return;
|
|
87
86
|
}
|
|
88
87
|
if (key.return) {
|
|
89
|
-
|
|
88
|
+
runState.toggleExpand();
|
|
90
89
|
return;
|
|
91
90
|
}
|
|
92
|
-
|
|
93
|
-
inspectState.cyclePaneMode();
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
});
|
|
91
|
+
}, { isActive: interactive });
|
|
97
92
|
|
|
98
|
-
const terminalWidth = getTerminalWidth(stdout, 100);
|
|
99
|
-
const leftWidth = Math.max(42, Math.floor(terminalWidth * 0.52));
|
|
100
|
-
const rightWidth = Math.max(28, terminalWidth - leftWidth - 1);
|
|
101
93
|
const visibleTreeEntries = useMemo(
|
|
102
|
-
() => buildTreeViewport(snapshot.visibleEntries, snapshot.selectedEntryId,
|
|
94
|
+
() => buildTreeViewport(snapshot.visibleEntries, snapshot.selectedEntryId, 24),
|
|
103
95
|
[snapshot.visibleEntries, snapshot.selectedEntryId]
|
|
104
96
|
);
|
|
105
|
-
const paneContent = useMemo(
|
|
106
|
-
() =>
|
|
107
|
-
readContextContent({
|
|
108
|
-
productDir,
|
|
109
|
-
snapshot,
|
|
110
|
-
mode: snapshot.paneMode,
|
|
111
|
-
}),
|
|
112
|
-
[productDir, snapshot]
|
|
113
|
-
);
|
|
114
97
|
const summaryLines = snapshot.finished && snapshot.summaryData
|
|
115
98
|
? renderSummaryBox(snapshot.summaryData.rows, { stdout })
|
|
116
99
|
: [];
|
|
@@ -122,15 +105,8 @@ export function InspectApp({
|
|
|
122
105
|
snapshot.notice ? createElement(Text, { key: "notice" }, yellow(snapshot.notice)) : null,
|
|
123
106
|
createElement(
|
|
124
107
|
Box,
|
|
125
|
-
{ key: "main", marginTop: 1, flexDirection: "
|
|
126
|
-
|
|
127
|
-
createElement(
|
|
128
|
-
Box,
|
|
129
|
-
{ width: rightWidth, flexDirection: "column", paddingLeft: 1 },
|
|
130
|
-
createElement(Text, { key: "pane-title" }, bold(paneContent.title)),
|
|
131
|
-
createElement(Text, { key: "pane-gap" }, ""),
|
|
132
|
-
...paneContent.lines.slice(0, 34).map((line, index) => createElement(Text, { key: `pane-${index}` }, line))
|
|
133
|
-
)
|
|
108
|
+
{ key: "main", marginTop: 1, flexDirection: "column" },
|
|
109
|
+
...visibleTreeEntries.map(renderTreeLine.bind(null, snapshot, spinnerFrame))
|
|
134
110
|
),
|
|
135
111
|
snapshot.filter.active ? createElement(Text, { key: "filter-gap" }, "") : null,
|
|
136
112
|
snapshot.filter.active ? createElement(FilterBar, { key: "filter-bar", filter: snapshot.filter }) : null,
|
|
@@ -144,7 +120,7 @@ export function InspectApp({
|
|
|
144
120
|
export function buildHeaderText(snapshot) {
|
|
145
121
|
const progressText = snapshot.totalCount > 0 ? `[${snapshot.completedCount}/${snapshot.totalCount}]` : "[0/0]";
|
|
146
122
|
const phaseText = snapshot.phase || (snapshot.finished ? "run complete" : "preparing");
|
|
147
|
-
const sourceText = snapshot.dataSource === "artifact" ? "artifact
|
|
123
|
+
const sourceText = snapshot.dataSource === "artifact" ? "artifact run" : snapshot.finished ? "live summary" : "live run";
|
|
148
124
|
const filterText = snapshot.filter.active ? `filter ${snapshot.filter.count}` : null;
|
|
149
125
|
return [progressText, phaseText, sourceText, filterText].filter(Boolean).join(" · ");
|
|
150
126
|
}
|
|
@@ -154,8 +130,7 @@ export function buildFooterText(snapshot) {
|
|
|
154
130
|
if (snapshot.filter.active) {
|
|
155
131
|
return "type to filter · ↑/↓ move · Esc clear filter · q quit";
|
|
156
132
|
}
|
|
157
|
-
|
|
158
|
-
return `${inspectKeys} · q quit`;
|
|
133
|
+
return "↑/↓ move · Enter collapse/expand · / filter · q quit";
|
|
159
134
|
}
|
|
160
135
|
|
|
161
136
|
function buildTreeViewport(entries, selectedEntryId, radius) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { createElement } from "react";
|
|
2
2
|
import { Text } from "ink";
|
|
3
|
-
import { bold, dim } from "
|
|
3
|
+
import { bold, dim } from "../../terminal/colors.mjs";
|
|
4
4
|
|
|
5
5
|
export function FilterBar({ filter } = {}) {
|
|
6
6
|
if (!filter?.active) return null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import figures from "figures";
|
|
2
|
-
import { clamp, getTerminalWidth, measureWidth, padEndVisible, wrapText } from "
|
|
2
|
+
import { clamp, getTerminalWidth, measureWidth, padEndVisible, wrapText } from "../../terminal/layout.mjs";
|
|
3
3
|
|
|
4
4
|
export function renderSummaryBox(
|
|
5
5
|
rows,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_CLI_CONFIG = Object.freeze({
|
|
5
|
+
autoCollapsePassedTreeBranches: true,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export function cliConfigPath(productDir) {
|
|
9
|
+
return path.join(productDir || process.cwd(), ".testkit", "config.json");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function loadCliConfig(productDir) {
|
|
13
|
+
try {
|
|
14
|
+
return normalizeCliConfig(JSON.parse(fs.readFileSync(cliConfigPath(productDir), "utf8")));
|
|
15
|
+
} catch {
|
|
16
|
+
return { ...DEFAULT_CLI_CONFIG };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function saveCliConfig(productDir, config) {
|
|
21
|
+
const filePath = cliConfigPath(productDir);
|
|
22
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
23
|
+
fs.writeFileSync(filePath, `${JSON.stringify(normalizeCliConfig(config), null, 2)}\n`, "utf8");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resetCliConfig(productDir) {
|
|
27
|
+
fs.rmSync(cliConfigPath(productDir), { force: true });
|
|
28
|
+
return { ...DEFAULT_CLI_CONFIG };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function mergeCliConfig(...configs) {
|
|
32
|
+
let merged = { ...DEFAULT_CLI_CONFIG };
|
|
33
|
+
for (const config of configs) {
|
|
34
|
+
if (!config || typeof config !== "object") continue;
|
|
35
|
+
merged = normalizeCliConfig({ ...merged, ...dropUndefined(config) });
|
|
36
|
+
}
|
|
37
|
+
return merged;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function normalizeCliConfig(config = {}) {
|
|
41
|
+
return {
|
|
42
|
+
autoCollapsePassedTreeBranches:
|
|
43
|
+
config.autoCollapsePassedTreeBranches == null
|
|
44
|
+
? DEFAULT_CLI_CONFIG.autoCollapsePassedTreeBranches
|
|
45
|
+
: Boolean(config.autoCollapsePassedTreeBranches),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatCliConfig(config) {
|
|
50
|
+
const normalized = normalizeCliConfig(config);
|
|
51
|
+
return [
|
|
52
|
+
`autoCollapsePassedTreeBranches: ${normalized.autoCollapsePassedTreeBranches}`,
|
|
53
|
+
`path: .testkit/config.json`,
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function dropUndefined(value) {
|
|
58
|
+
const result = {};
|
|
59
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
60
|
+
if (entry !== undefined) result[key] = entry;
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
package/lib/cli/entrypoint.mjs
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export function normalizeCliArgs(argv) {
|
|
2
|
+
if (argv[0] === "help") return normalizeHelpInvocation(argv);
|
|
3
|
+
if (isOclifBuiltinInvocation(argv)) return argv;
|
|
4
|
+
|
|
2
5
|
const topLevelCommands = new Set([
|
|
3
6
|
"assistant",
|
|
4
7
|
"run",
|
|
@@ -10,11 +13,6 @@ export function normalizeCliArgs(argv) {
|
|
|
10
13
|
"doctor",
|
|
11
14
|
"browser",
|
|
12
15
|
"db",
|
|
13
|
-
"help",
|
|
14
|
-
"--help",
|
|
15
|
-
"-h",
|
|
16
|
-
"--version",
|
|
17
|
-
"-v",
|
|
18
16
|
]);
|
|
19
17
|
const runTypeShortcuts = new Set(["int", "e2e", "scenario", "dal", "load", "pw", "all"]);
|
|
20
18
|
const valueFlags = new Set([
|
|
@@ -95,6 +93,17 @@ export function normalizeCliArgs(argv) {
|
|
|
95
93
|
return argv;
|
|
96
94
|
}
|
|
97
95
|
|
|
96
|
+
function isOclifBuiltinInvocation(argv) {
|
|
97
|
+
const first = argv[0];
|
|
98
|
+
if (first === "--help" || first === "-h" || first === "--version" || first === "-v") return true;
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function normalizeHelpInvocation(argv) {
|
|
103
|
+
if (!argv[1]) return ["--help"];
|
|
104
|
+
return [argv[1], ...argv.slice(2), "--help"];
|
|
105
|
+
}
|
|
106
|
+
|
|
98
107
|
function findPositionals(args, flagsWithValues) {
|
|
99
108
|
const positionals = [];
|
|
100
109
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { startBrowserBridgeServer } from "@elench/testkit-bridge";
|
|
2
|
+
import { loadBrowserBridgeContext } from "../../../../app/browser-bridge.mjs";
|
|
3
|
+
|
|
4
|
+
export async function executeBrowserServeOperation(flags = {}) {
|
|
5
|
+
const { productDir, context } = await loadBrowserBridgeContext({ dir: flags.dir });
|
|
6
|
+
|
|
7
|
+
const adapter = {
|
|
8
|
+
loadProductContext: async () => context,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const serverRef = await startBrowserBridgeServer(adapter, {
|
|
12
|
+
host: flags.host,
|
|
13
|
+
port: flags.port,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
ok: true,
|
|
18
|
+
productDir,
|
|
19
|
+
host: serverRef.host,
|
|
20
|
+
port: serverRef.port,
|
|
21
|
+
url: serverRef.url,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as runner from "../../../runner/index.mjs";
|
|
2
|
+
import { loadManagedConfigs } from "../../../app/configs.mjs";
|
|
3
|
+
|
|
4
|
+
export async function executeCleanupOperation(flags = {}) {
|
|
5
|
+
const { allConfigs } = await loadManagedConfigs({ dir: flags.dir, service: flags.service });
|
|
6
|
+
const productDir = allConfigs[0]?.productDir || process.cwd();
|
|
7
|
+
return runner.cleanup(productDir);
|
|
8
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import os from "os";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { loadManagedConfigs, resolveTargetConfig, collectRequiredConfigs, topologicallySortConfigs } from "
|
|
5
|
-
import { resolveProductDir } from "
|
|
6
|
-
import { captureDatabaseTemplateSnapshot, prepareDatabaseRuntime } from "
|
|
7
|
-
import { createRunReporter } from "
|
|
8
|
-
import { createRunLogRegistry } from "
|
|
9
|
-
import { createSetupOperationRegistry } from "
|
|
10
|
-
import { resolveRuntimeInstanceConfigs } from "
|
|
4
|
+
import { loadManagedConfigs, resolveTargetConfig, collectRequiredConfigs, topologicallySortConfigs } from "../../../../../app/configs.mjs";
|
|
5
|
+
import { resolveProductDir } from "../../../../../config/index.mjs";
|
|
6
|
+
import { captureDatabaseTemplateSnapshot, prepareDatabaseRuntime } from "../../../../../database/index.mjs";
|
|
7
|
+
import { createRunReporter } from "../../../../renderers/run/text-reporter.mjs";
|
|
8
|
+
import { createRunLogRegistry } from "../../../../../runner/logs.mjs";
|
|
9
|
+
import { createSetupOperationRegistry } from "../../../../../runner/setup-operations.mjs";
|
|
10
|
+
import { resolveRuntimeInstanceConfigs } from "../../../../../runner/template.mjs";
|
|
11
11
|
|
|
12
|
-
export async function
|
|
12
|
+
export async function executeDatabaseSnapshotCaptureOperation(options = {}) {
|
|
13
13
|
const productDir = resolveProductDir(process.cwd(), options.dir);
|
|
14
14
|
const { configs } = await loadManagedConfigs({ dir: productDir });
|
|
15
15
|
const target = resolveTargetConfig(configs, options.service);
|
|
@@ -51,7 +51,13 @@ export async function runDatabaseSnapshotCaptureCommand(options = {}) {
|
|
|
51
51
|
logRegistry,
|
|
52
52
|
setupRegistry,
|
|
53
53
|
});
|
|
54
|
-
|
|
54
|
+
return {
|
|
55
|
+
ok: true,
|
|
56
|
+
productDir,
|
|
57
|
+
service: target.name,
|
|
58
|
+
outputPath: absoluteOutputPath,
|
|
59
|
+
outputLabel: path.relative(productDir, absoluteOutputPath) || path.basename(absoluteOutputPath),
|
|
60
|
+
};
|
|
55
61
|
} finally {
|
|
56
62
|
logRegistry.closeAll();
|
|
57
63
|
fs.rmSync(runtimeRoot, { recursive: true, force: true });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as runner from "../../../runner/index.mjs";
|
|
2
|
+
import { loadManagedConfigs } from "../../../app/configs.mjs";
|
|
3
|
+
|
|
4
|
+
export async function executeDestroyOperation(flags = {}) {
|
|
5
|
+
const { configs } = await loadManagedConfigs({ dir: flags.dir, service: flags.service });
|
|
6
|
+
const results = [];
|
|
7
|
+
for (const config of configs) {
|
|
8
|
+
await runner.destroy(config);
|
|
9
|
+
results.push({ name: config.name, destroyed: true });
|
|
10
|
+
}
|
|
11
|
+
return results;
|
|
12
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { discoverTests } from "../../../discovery/index.mjs";
|
|
4
|
+
import { resolveProductDir } from "../../../config/index.mjs";
|
|
5
|
+
import { resolveRequestedFiles } from "../../args.mjs";
|
|
6
|
+
|
|
7
|
+
export async function executeDiscoverOperation(flags = {}, cwd = process.cwd()) {
|
|
8
|
+
const productDir = resolveProductDir(cwd, flags.dir);
|
|
9
|
+
const fileNames = resolveRequestedFiles(flags.file || [], productDir, cwd);
|
|
10
|
+
const result = await discoverTests({
|
|
11
|
+
dir: productDir,
|
|
12
|
+
service: flags.service || null,
|
|
13
|
+
type: flags.type || [],
|
|
14
|
+
suite: flags.suite || [],
|
|
15
|
+
file: fileNames,
|
|
16
|
+
runnableOnly: flags["runnable-only"],
|
|
17
|
+
diagnostics: flags.strict ? "error" : "report",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
let outputLabel = null;
|
|
21
|
+
if (flags.output) {
|
|
22
|
+
const outputPath = path.resolve(productDir, flags.output);
|
|
23
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
24
|
+
fs.writeFileSync(outputPath, `${JSON.stringify(result, null, 2)}\n`);
|
|
25
|
+
outputLabel = path.relative(productDir, outputPath) || path.basename(outputPath);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
...result,
|
|
30
|
+
outputLabel,
|
|
31
|
+
};
|
|
32
|
+
}
|