@elench/testkit 0.1.52 → 0.1.54

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 (52) hide show
  1. package/README.md +14 -0
  2. package/bin/testkit.mjs +4 -6
  3. package/lib/cli/command-helpers.mjs +170 -0
  4. package/lib/cli/commands/artifacts.mjs +45 -0
  5. package/lib/cli/commands/cleanup.mjs +15 -0
  6. package/lib/cli/commands/db/snapshot/capture.mjs +22 -0
  7. package/lib/cli/commands/destroy.mjs +15 -0
  8. package/lib/cli/commands/known-failures/render.mjs +19 -0
  9. package/lib/cli/commands/known-failures/validate.mjs +20 -0
  10. package/lib/cli/commands/logs.mjs +47 -0
  11. package/lib/cli/commands/run.mjs +23 -0
  12. package/lib/cli/commands/show.mjs +47 -0
  13. package/lib/cli/commands/status.mjs +15 -0
  14. package/lib/cli/commands/watch.mjs +23 -0
  15. package/lib/cli/entrypoint.mjs +83 -0
  16. package/lib/cli/index.mjs +6 -116
  17. package/lib/cli/presentation/code-frames.mjs +57 -0
  18. package/lib/cli/presentation/code-frames.test.mjs +71 -0
  19. package/lib/cli/presentation/colors.mjs +29 -0
  20. package/lib/cli/presentation/run-reporter.mjs +100 -0
  21. package/lib/cli/tui/watch-app.mjs +104 -0
  22. package/lib/cli/viewer.mjs +268 -0
  23. package/lib/known-failures/index.mjs +1 -1
  24. package/lib/known-failures/index.test.mjs +46 -0
  25. package/lib/runner/artifacts.mjs +35 -0
  26. package/lib/runner/default-runtime-errors.mjs +66 -0
  27. package/lib/runner/default-runtime-runner.mjs +52 -11
  28. package/lib/runner/failure-details.mjs +31 -0
  29. package/lib/runner/failure-details.test.mjs +51 -0
  30. package/lib/runner/formatting.mjs +207 -0
  31. package/lib/runner/formatting.test.mjs +81 -6
  32. package/lib/runner/logs.mjs +89 -0
  33. package/lib/runner/orchestrator.mjs +51 -20
  34. package/lib/runner/playwright-runner.mjs +15 -7
  35. package/lib/runner/processes.mjs +9 -11
  36. package/lib/runner/reporting.mjs +5 -1
  37. package/lib/runner/reporting.test.mjs +4 -1
  38. package/lib/runner/runtime-contexts.mjs +7 -3
  39. package/lib/runner/runtime-manager.mjs +8 -2
  40. package/lib/runner/runtime-preparation.mjs +9 -4
  41. package/lib/runner/services.mjs +25 -8
  42. package/lib/runner/template-steps.mjs +4 -3
  43. package/lib/runner/triage.mjs +67 -0
  44. package/lib/runner/worker-loop.mjs +8 -7
  45. package/lib/runtime/index.d.ts +60 -0
  46. package/lib/runtime/index.mjs +12 -0
  47. package/lib/runtime-src/k6/checks.js +45 -12
  48. package/lib/runtime-src/k6/http-assertions.js +214 -0
  49. package/lib/runtime-src/k6/http.js +261 -13
  50. package/lib/runtime-src/k6/suite.js +46 -1
  51. package/lib/toolchains/index.mjs +6 -3
  52. package/package.json +13 -3
package/README.md CHANGED
@@ -47,6 +47,14 @@ npx @elench/testkit --type int --write-status
47
47
  # Lifecycle
48
48
  npx @elench/testkit status
49
49
  npx @elench/testkit destroy
50
+ npx @elench/testkit cleanup
51
+
52
+ # Inspect the latest run artifact
53
+ npx @elench/testkit show
54
+ npx @elench/testkit show __testkit__/health/health.int.testkit.ts
55
+ npx @elench/testkit artifacts __testkit__/health/health.int.testkit.ts
56
+ npx @elench/testkit logs __testkit__/health/health.int.testkit.ts
57
+ npx @elench/testkit watch
50
58
 
51
59
  # Known-failures tooling
52
60
  npx @elench/testkit known-failures validate --issue-mode error
@@ -56,6 +64,12 @@ npx @elench/testkit known-failures render --output KNOWN_FAILURES.md
56
64
  npx @elench/testkit db snapshot capture --service api --output scripts/testkit/schema-baseline.sql
57
65
  ```
58
66
 
67
+ `testkit` now keeps the default terminal output intentionally short: one line
68
+ per completed file, a concise failure block, and a final summary. Service logs,
69
+ captured runtime output, emitted artifacts, and user-visible LLM responses are
70
+ persisted under `.testkit/results/` and inspected on demand with `show`,
71
+ `artifacts`, `logs`, or `watch`.
72
+
59
73
  ## Setup
60
74
 
61
75
  Create `testkit.setup.ts` at repo root:
package/bin/testkit.mjs CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { run } from "../lib/cli/index.mjs";
2
+ import { execute } from "@oclif/core";
3
+ import { normalizeCliArgs } from "../lib/cli/entrypoint.mjs";
3
4
 
4
- run().catch((error) => {
5
- setImmediate(() => {
6
- throw error;
7
- });
8
- });
5
+ const effectiveArgs = normalizeCliArgs(process.argv.slice(2));
6
+ await execute({ args: effectiveArgs, dir: import.meta.url });
@@ -0,0 +1,170 @@
1
+ import path from "path";
2
+ import { Flags } from "@oclif/core";
3
+ import { loadConfigs } from "../config/index.mjs";
4
+ import {
5
+ parseFileTimeoutOption,
6
+ parseShardOption,
7
+ parseSuiteOption,
8
+ parseTypeOption,
9
+ parseWorkersOption,
10
+ resolveRequestedFiles,
11
+ } from "./args.mjs";
12
+ import * as runner from "../runner/index.mjs";
13
+ import { createRunReporter } from "./presentation/run-reporter.mjs";
14
+
15
+ export const sharedFlags = {
16
+ dir: Flags.string({
17
+ description: "Explicit product directory",
18
+ }),
19
+ service: Flags.string({
20
+ description: "Run or inspect only one service",
21
+ }),
22
+ };
23
+
24
+ export const runFlags = {
25
+ ...sharedFlags,
26
+ type: Flags.string({
27
+ char: "t",
28
+ multiple: true,
29
+ description: "Run specific suite type(s): int, e2e, dal, load, pw, all",
30
+ }),
31
+ suite: Flags.string({
32
+ char: "s",
33
+ multiple: true,
34
+ description: "Run specific suite(s)",
35
+ }),
36
+ file: Flags.string({
37
+ char: "f",
38
+ multiple: true,
39
+ description: "Run specific file(s)",
40
+ }),
41
+ workers: Flags.string({
42
+ description: "Number of test executors for the whole run",
43
+ }),
44
+ "file-timeout-seconds": Flags.string({
45
+ description: "Per-file wall-clock timeout in seconds",
46
+ }),
47
+ shard: Flags.string({
48
+ description: "Run only shard i of n at suite granularity",
49
+ }),
50
+ "write-status": Flags.boolean({
51
+ description: "Write a deterministic testkit.status.json snapshot",
52
+ default: false,
53
+ }),
54
+ "allow-partial-status": Flags.boolean({
55
+ description: "Allow --write-status for filtered runs",
56
+ default: false,
57
+ }),
58
+ "ignore-skip-rules": Flags.boolean({
59
+ description: "Run files even if testkit.setup.ts marks them skipped",
60
+ default: false,
61
+ }),
62
+ "output-mode": Flags.string({
63
+ description: "Reporter mode",
64
+ options: ["compact", "debug"],
65
+ }),
66
+ debug: Flags.boolean({
67
+ description: "Alias for --output-mode debug",
68
+ default: false,
69
+ }),
70
+ };
71
+
72
+ export async function resolveConfigsForCommand(flags) {
73
+ const allConfigs = await loadConfigs({ dir: flags.dir });
74
+ const configs = flags.service
75
+ ? allConfigs.filter((config) => config.name === flags.service)
76
+ : allConfigs;
77
+ if (flags.service && configs.length === 0) {
78
+ const available = allConfigs.map((config) => config.name).join(", ");
79
+ throw new Error(`Service "${flags.service}" not found. Available: ${available}`);
80
+ }
81
+ return { allConfigs, configs };
82
+ }
83
+
84
+ export async function executeRunCommand(command, flags, positionalType = null) {
85
+ const { allConfigs, configs } = await resolveConfigsForCommand(flags);
86
+ const workers = flags.workers == null ? null : parseWorkersOption(flags.workers);
87
+ const fileTimeoutSeconds =
88
+ flags["file-timeout-seconds"] == null
89
+ ? null
90
+ : parseFileTimeoutOption(flags["file-timeout-seconds"]);
91
+ const shard = parseShardOption(flags.shard);
92
+ const typeValues = parseTypeOption(flags.type, positionalType);
93
+ const suiteSelectors = parseSuiteOption(flags.suite);
94
+ const rawFileNames = Array.isArray(flags.file) ? flags.file : [flags.file].filter(Boolean);
95
+ const productDir = allConfigs[0]?.productDir || process.cwd();
96
+ const fileNames = resolveRequestedFiles(rawFileNames, productDir, process.cwd());
97
+ const outputMode = command.jsonEnabled()
98
+ ? "json"
99
+ : flags.debug
100
+ ? "debug"
101
+ : flags["output-mode"] || "compact";
102
+ const reporter = createRunReporter({ outputMode });
103
+ const result = await runner.runAll(
104
+ configs,
105
+ typeValues,
106
+ suiteSelectors,
107
+ {
108
+ ...flags,
109
+ typeValues,
110
+ fileNames,
111
+ workers,
112
+ fileTimeoutSeconds,
113
+ shard,
114
+ serviceFilter: flags.service || null,
115
+ reporter,
116
+ writeStatus: flags["write-status"],
117
+ allowPartialStatus: flags["allow-partial-status"],
118
+ ignoreSkipRules: flags["ignore-skip-rules"],
119
+ },
120
+ allConfigs
121
+ );
122
+ return {
123
+ outputMode,
124
+ ...result,
125
+ };
126
+ }
127
+
128
+ export async function runStatusLike(commandName, flags) {
129
+ const { allConfigs, configs } = await resolveConfigsForCommand(flags);
130
+
131
+ if (commandName === "cleanup") {
132
+ await runner.cleanup(allConfigs[0]?.productDir || process.cwd());
133
+ return { ok: true };
134
+ }
135
+
136
+ const productResults = [];
137
+ for (const config of configs) {
138
+ if (commandName === "status") {
139
+ productResults.push(runner.showStatus(config));
140
+ continue;
141
+ }
142
+ await runner.destroy(config);
143
+ productResults.push({ name: config.name, destroyed: true });
144
+ }
145
+
146
+ return { ok: true, results: productResults };
147
+ }
148
+
149
+ export function makeKnownFailuresFlags() {
150
+ return {
151
+ ...sharedFlags,
152
+ input: Flags.string({
153
+ description: "Known failures JSON path (repo-relative)",
154
+ }),
155
+ output: Flags.string({
156
+ description: "Output path",
157
+ }),
158
+ status: Flags.string({
159
+ description: "Status artifact path",
160
+ }),
161
+ "issue-mode": Flags.string({
162
+ description: "Issue validation mode override: off, warn, error",
163
+ options: ["off", "warn", "error"],
164
+ }),
165
+ };
166
+ }
167
+
168
+ export function relativeToProduct(productDir, targetPath) {
169
+ return path.relative(productDir, targetPath);
170
+ }
@@ -0,0 +1,45 @@
1
+ import { Args, Command } from "@oclif/core";
2
+ import { sharedFlags } from "../command-helpers.mjs";
3
+ import { collectArtifactEntries, loadLatestRunArtifact } from "../viewer.mjs";
4
+
5
+ export default class ArtifactsCommand extends Command {
6
+ static summary = "List persisted artifacts from the latest run";
7
+
8
+ static enableJsonFlag = true;
9
+
10
+ static args = {
11
+ file: Args.string({
12
+ description: "Optional file path to filter artifacts",
13
+ required: false,
14
+ }),
15
+ };
16
+
17
+ static flags = sharedFlags;
18
+
19
+ async run() {
20
+ const { args, flags } = await this.parse(ArtifactsCommand);
21
+ const productDir = flags.dir || process.cwd();
22
+ const runArtifact = loadLatestRunArtifact(productDir);
23
+ const entries = collectArtifactEntries(productDir, runArtifact, args.file || null, flags.service || null)
24
+ .map((entry) => ({
25
+ service: entry.service.name,
26
+ suite: `${entry.suite.type}:${entry.suite.name}`,
27
+ file: entry.file.path,
28
+ name: entry.artifactRef.name,
29
+ kind: entry.artifactRef.kind,
30
+ summary: entry.artifactRef.summary,
31
+ path: entry.artifactRef.path,
32
+ }));
33
+
34
+ if (!this.jsonEnabled()) {
35
+ for (const entry of entries) {
36
+ this.log(`${entry.file}`);
37
+ this.log(` ${entry.name}${entry.kind ? ` [${entry.kind}]` : ""}`);
38
+ if (entry.summary) this.log(` ${entry.summary}`);
39
+ this.log(` ${entry.path}`);
40
+ }
41
+ }
42
+
43
+ return entries;
44
+ }
45
+ }
@@ -0,0 +1,15 @@
1
+ import { Command } from "@oclif/core";
2
+ import { runStatusLike, sharedFlags } from "../command-helpers.mjs";
3
+
4
+ export default class CleanupCommand extends Command {
5
+ static summary = "Clean stale local testkit state";
6
+
7
+ static enableJsonFlag = true;
8
+
9
+ static flags = sharedFlags;
10
+
11
+ async run() {
12
+ const { flags } = await this.parse(CleanupCommand);
13
+ return runStatusLike("cleanup", flags);
14
+ }
15
+ }
@@ -0,0 +1,22 @@
1
+ import { Command, Flags } from "@oclif/core";
2
+ import { sharedFlags } from "../../../command-helpers.mjs";
3
+ import { runDatabaseSnapshotCaptureCommand } from "../../../db.mjs";
4
+
5
+ export default class DbSnapshotCaptureCommand extends Command {
6
+ static summary = "Capture a database schema snapshot";
7
+
8
+ static enableJsonFlag = true;
9
+
10
+ static flags = {
11
+ ...sharedFlags,
12
+ output: Flags.string({
13
+ description: "Output path for the snapshot",
14
+ }),
15
+ };
16
+
17
+ async run() {
18
+ const { flags } = await this.parse(DbSnapshotCaptureCommand);
19
+ await runDatabaseSnapshotCaptureCommand(flags);
20
+ return { ok: true };
21
+ }
22
+ }
@@ -0,0 +1,15 @@
1
+ import { Command } from "@oclif/core";
2
+ import { runStatusLike, sharedFlags } from "../command-helpers.mjs";
3
+
4
+ export default class DestroyCommand extends Command {
5
+ static summary = "Destroy local testkit state";
6
+
7
+ static enableJsonFlag = true;
8
+
9
+ static flags = sharedFlags;
10
+
11
+ async run() {
12
+ const { flags } = await this.parse(DestroyCommand);
13
+ return runStatusLike("destroy", flags);
14
+ }
15
+ }
@@ -0,0 +1,19 @@
1
+ import { Command } from "@oclif/core";
2
+ import { makeKnownFailuresFlags } from "../../command-helpers.mjs";
3
+ import { runKnownFailuresRenderCommand } from "../../known-failures.mjs";
4
+
5
+ export default class KnownFailuresRenderCommand extends Command {
6
+ static summary = "Render known failures markdown";
7
+
8
+ static enableJsonFlag = true;
9
+
10
+ static flags = makeKnownFailuresFlags();
11
+
12
+ async run() {
13
+ const { flags } = await this.parse(KnownFailuresRenderCommand);
14
+ await runKnownFailuresRenderCommand({
15
+ ...flags,
16
+ });
17
+ return { ok: true };
18
+ }
19
+ }
@@ -0,0 +1,20 @@
1
+ import { Command } from "@oclif/core";
2
+ import { makeKnownFailuresFlags } from "../../command-helpers.mjs";
3
+ import { runKnownFailuresValidateCommand } from "../../known-failures.mjs";
4
+
5
+ export default class KnownFailuresValidateCommand extends Command {
6
+ static summary = "Validate known failures against the latest status artifact";
7
+
8
+ static enableJsonFlag = true;
9
+
10
+ static flags = makeKnownFailuresFlags();
11
+
12
+ async run() {
13
+ const { flags } = await this.parse(KnownFailuresValidateCommand);
14
+ await runKnownFailuresValidateCommand({
15
+ ...flags,
16
+ issueMode: flags["issue-mode"] || null,
17
+ });
18
+ return { ok: true };
19
+ }
20
+ }
@@ -0,0 +1,47 @@
1
+ import { Args, Command, Flags } from "@oclif/core";
2
+ import { sharedFlags } from "../command-helpers.mjs";
3
+ import { readLogTail } from "../../runner/logs.mjs";
4
+ import { getServiceLogRefs, loadLatestRunArtifact, resolveFileSubject } from "../viewer.mjs";
5
+ import path from "path";
6
+
7
+ export default class LogsCommand extends Command {
8
+ static summary = "Show backend log tails relevant to one file from the latest run";
9
+
10
+ static enableJsonFlag = true;
11
+
12
+ static args = {
13
+ file: Args.string({
14
+ description: "Optional file path; defaults to the first failed file",
15
+ required: false,
16
+ }),
17
+ };
18
+
19
+ static flags = {
20
+ ...sharedFlags,
21
+ tail: Flags.integer({
22
+ description: "Number of lines to show from each log",
23
+ default: 40,
24
+ }),
25
+ };
26
+
27
+ async run() {
28
+ const { args, flags } = await this.parse(LogsCommand);
29
+ const productDir = flags.dir || process.cwd();
30
+ const runArtifact = loadLatestRunArtifact(productDir);
31
+ const subject = resolveFileSubject(runArtifact, args.file || null, flags.service || null);
32
+ const logs = getServiceLogRefs(runArtifact, subject.service.name).map((entry) => ({
33
+ ...entry,
34
+ lines: readLogTail(path.join(productDir, entry.path), flags.tail),
35
+ }));
36
+
37
+ if (!this.jsonEnabled()) {
38
+ for (const entry of logs) {
39
+ this.log(`${entry.runtimeLabel}`);
40
+ this.log(` ${entry.path}`);
41
+ for (const line of entry.lines) this.log(` ${line}`);
42
+ }
43
+ }
44
+
45
+ return logs;
46
+ }
47
+ }
@@ -0,0 +1,23 @@
1
+ import { Args, Command } from "@oclif/core";
2
+ import { executeRunCommand, runFlags } from "../command-helpers.mjs";
3
+
4
+ export default class RunCommand extends Command {
5
+ static summary = "Run test suites";
6
+
7
+ static enableJsonFlag = true;
8
+
9
+ static args = {
10
+ type: Args.string({
11
+ description: "Optional suite type shortcut: int, e2e, dal, load, pw, all",
12
+ required: false,
13
+ options: ["int", "e2e", "dal", "load", "pw", "all"],
14
+ }),
15
+ };
16
+
17
+ static flags = runFlags;
18
+
19
+ async run() {
20
+ const { args, flags } = await this.parse(RunCommand);
21
+ return executeRunCommand(this, flags, args.type || null);
22
+ }
23
+ }
@@ -0,0 +1,47 @@
1
+ import { Args, Command, Flags } from "@oclif/core";
2
+ import { sharedFlags } from "../command-helpers.mjs";
3
+ import { formatFileDetail, loadLatestRunArtifact, resolveFileSubject } from "../viewer.mjs";
4
+
5
+ export default class ShowCommand extends Command {
6
+ static summary = "Show the most useful details for one file from the latest run";
7
+
8
+ static enableJsonFlag = true;
9
+
10
+ static args = {
11
+ file: Args.string({
12
+ description: "File path to inspect; defaults to the first failed file",
13
+ required: false,
14
+ }),
15
+ };
16
+
17
+ static flags = {
18
+ ...sharedFlags,
19
+ "log-tail": Flags.integer({
20
+ description: "Number of backend log lines to include",
21
+ default: 12,
22
+ }),
23
+ };
24
+
25
+ async run() {
26
+ const { args, flags } = await this.parse(ShowCommand);
27
+ const productDir = flags.dir || process.cwd();
28
+ const runArtifact = loadLatestRunArtifact(productDir);
29
+ const subject = resolveFileSubject(runArtifact, args.file || null, flags.service || null);
30
+ const result = {
31
+ file: subject.file,
32
+ suite: {
33
+ name: subject.suite.name,
34
+ type: subject.suite.type,
35
+ },
36
+ service: {
37
+ name: subject.service.name,
38
+ },
39
+ };
40
+ if (!this.jsonEnabled()) {
41
+ for (const line of formatFileDetail(productDir, runArtifact, subject, { logTail: flags["log-tail"] })) {
42
+ this.log(line);
43
+ }
44
+ }
45
+ return result;
46
+ }
47
+ }
@@ -0,0 +1,15 @@
1
+ import { Command } from "@oclif/core";
2
+ import { runStatusLike, sharedFlags } from "../command-helpers.mjs";
3
+
4
+ export default class StatusCommand extends Command {
5
+ static summary = "Show local testkit state";
6
+
7
+ static enableJsonFlag = true;
8
+
9
+ static flags = sharedFlags;
10
+
11
+ async run() {
12
+ const { flags } = await this.parse(StatusCommand);
13
+ return runStatusLike("status", flags);
14
+ }
15
+ }
@@ -0,0 +1,23 @@
1
+ import React, { createElement } from "react";
2
+ import { Command } from "@oclif/core";
3
+ import { render } from "ink";
4
+ import { sharedFlags } from "../command-helpers.mjs";
5
+ import { WatchApp } from "../tui/watch-app.mjs";
6
+
7
+ export default class WatchCommand extends Command {
8
+ static summary = "Open an interactive viewer for the latest run artifact";
9
+
10
+ static flags = sharedFlags;
11
+
12
+ async run() {
13
+ const { flags } = await this.parse(WatchCommand);
14
+ const productDir = flags.dir || process.cwd();
15
+ const app = render(
16
+ createElement(WatchApp, {
17
+ productDir,
18
+ serviceFilter: flags.service || null,
19
+ })
20
+ );
21
+ await app.waitUntilExit();
22
+ }
23
+ }
@@ -0,0 +1,83 @@
1
+ export function normalizeCliArgs(argv) {
2
+ const topLevelCommands = new Set([
3
+ "run",
4
+ "status",
5
+ "destroy",
6
+ "cleanup",
7
+ "show",
8
+ "logs",
9
+ "artifacts",
10
+ "watch",
11
+ "known-failures",
12
+ "db",
13
+ "help",
14
+ "--help",
15
+ "-h",
16
+ "--version",
17
+ "-v",
18
+ ]);
19
+ const runTypeShortcuts = new Set(["int", "e2e", "dal", "load", "pw", "all"]);
20
+ const valueFlags = new Set([
21
+ "--dir",
22
+ "--service",
23
+ "--type",
24
+ "--suite",
25
+ "--file",
26
+ "--workers",
27
+ "--file-timeout-seconds",
28
+ "--shard",
29
+ "--input",
30
+ "--output",
31
+ "--status",
32
+ "--issue-mode",
33
+ "--output-mode",
34
+ "--tail",
35
+ "--log-tail",
36
+ ]);
37
+ const positionals = findPositionals(argv, valueFlags);
38
+ const firstPositional = positionals[0] || null;
39
+ const shouldPrefixRun =
40
+ !firstPositional ||
41
+ runTypeShortcuts.has(firstPositional.value) ||
42
+ !topLevelCommands.has(firstPositional.value);
43
+
44
+ if (shouldPrefixRun) {
45
+ return ["run", ...argv];
46
+ }
47
+
48
+ if (topLevelCommands.has(firstPositional.value) && argv[0] !== firstPositional.value) {
49
+ return reorderCommandArgs(argv, positionals);
50
+ }
51
+
52
+ return argv;
53
+ }
54
+
55
+ function findPositionals(args, flagsWithValues) {
56
+ const positionals = [];
57
+ for (let index = 0; index < args.length; index += 1) {
58
+ const value = args[index];
59
+ if (!value.startsWith("-")) {
60
+ positionals.push({ index, value });
61
+ continue;
62
+ }
63
+ if (flagsWithValues.has(value)) {
64
+ index += 1;
65
+ }
66
+ }
67
+ return positionals;
68
+ }
69
+
70
+ function reorderCommandArgs(args, positionals) {
71
+ const commandTokens = [positionals[0]];
72
+ if (positionals[0]?.value === "known-failures" && positionals[1]) {
73
+ commandTokens.push(positionals[1]);
74
+ }
75
+ if (positionals[0]?.value === "db" && positionals[1] && positionals[2]) {
76
+ commandTokens.push(positionals[1], positionals[2]);
77
+ }
78
+ const commandIndexes = new Set(commandTokens.map((token) => token.index));
79
+ return [
80
+ ...commandTokens.map((token) => token.value),
81
+ ...args.filter((_value, index) => !commandIndexes.has(index)),
82
+ ];
83
+ }