@elench/testkit 0.1.52 → 0.1.53

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 (37) 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/run-reporter.mjs +91 -0
  18. package/lib/cli/tui/watch-app.mjs +104 -0
  19. package/lib/cli/viewer.mjs +163 -0
  20. package/lib/runner/artifacts.mjs +35 -0
  21. package/lib/runner/default-runtime-runner.mjs +44 -10
  22. package/lib/runner/formatting.mjs +97 -0
  23. package/lib/runner/formatting.test.mjs +4 -6
  24. package/lib/runner/logs.mjs +72 -0
  25. package/lib/runner/orchestrator.mjs +41 -19
  26. package/lib/runner/playwright-runner.mjs +15 -7
  27. package/lib/runner/processes.mjs +9 -11
  28. package/lib/runner/reporting.mjs +5 -1
  29. package/lib/runner/reporting.test.mjs +4 -1
  30. package/lib/runner/runtime-contexts.mjs +7 -3
  31. package/lib/runner/runtime-manager.mjs +8 -2
  32. package/lib/runner/runtime-preparation.mjs +9 -4
  33. package/lib/runner/services.mjs +25 -8
  34. package/lib/runner/template-steps.mjs +4 -3
  35. package/lib/runner/worker-loop.mjs +8 -7
  36. package/lib/toolchains/index.mjs +6 -3
  37. package/package.json +11 -3
@@ -6,16 +6,16 @@ import {
6
6
  } from "../toolchains/index.mjs";
7
7
  import { buildExecutionEnv, numericPortFromUrl } from "./template.mjs";
8
8
  import { DEFAULT_READY_TIMEOUT_MS, assertLocalServicePortsAvailable, isPortInUse, waitForReady } from "./readiness.mjs";
9
- import { killChildProcess, pipeOutput, startDetachedCommand, stopChildProcess, sleep } from "./processes.mjs";
9
+ import { captureOutput, killChildProcess, startDetachedCommand, stopChildProcess, sleep } from "./processes.mjs";
10
10
  import { readDatabaseUrl } from "./state-io.mjs";
11
11
 
12
- export async function startLocalServices(runtimeConfigs, lifecycle) {
12
+ export async function startLocalServices(runtimeConfigs, lifecycle, options = {}) {
13
13
  const started = [];
14
14
 
15
15
  try {
16
16
  for (const config of runtimeConfigs) {
17
17
  if (!config.testkit.local) continue;
18
- const proc = await startLocalService(config, lifecycle);
18
+ const proc = await startLocalService(config, lifecycle, options);
19
19
  started.push(proc);
20
20
  }
21
21
  } catch (error) {
@@ -26,10 +26,10 @@ export async function startLocalServices(runtimeConfigs, lifecycle) {
26
26
  return started;
27
27
  }
28
28
 
29
- export async function startLocalService(config, lifecycle) {
29
+ export async function startLocalService(config, lifecycle, options = {}) {
30
30
  const cwd = resolveServiceCwd(config.productDir, config.testkit.local.cwd);
31
31
  const resolvedToolchain = await resolveConfiguredToolchain(config);
32
- await announceResolvedToolchain(config, resolvedToolchain);
32
+ await announceResolvedToolchain(config, resolvedToolchain, options.reporter);
33
33
  const env = applyToolchainEnv(
34
34
  buildExecutionEnv(config, config.testkit.local.env, process.env),
35
35
  resolvedToolchain
@@ -46,12 +46,29 @@ export async function startLocalService(config, lifecycle) {
46
46
 
47
47
  await assertLocalServicePortsAvailable(config, isPortInUse);
48
48
 
49
- console.log(`Starting ${config.runtimeLabel}:${config.name}: ${config.testkit.local.start}`);
49
+ options.reporter?.localServiceStarting?.(config, config.testkit.local.start);
50
50
  const child = startDetachedCommand(config.testkit.local.start, cwd, env);
51
+ const logRecord = options.logRegistry?.ensureServiceLogRecord(config);
52
+ const liveWriter =
53
+ options.reporter?.outputMode === "debug"
54
+ ? (line) => options.reporter.writeDebugLine?.(line)
55
+ : null;
51
56
 
52
57
  const outputDrains = [
53
- pipeOutput(child.stdout, `[${config.runtimeLabel}:${config.name}]`),
54
- pipeOutput(child.stderr, `[${config.runtimeLabel}:${config.name}]`),
58
+ captureOutput(child.stdout, {
59
+ livePrefix: `[${config.runtimeLabel}:${config.name}]`,
60
+ liveWriter,
61
+ onLine(line) {
62
+ if (logRecord) options.logRegistry.append(logRecord, "stdout", line);
63
+ },
64
+ }),
65
+ captureOutput(child.stderr, {
66
+ livePrefix: `[${config.runtimeLabel}:${config.name}]`,
67
+ liveWriter,
68
+ onLine(line) {
69
+ if (logRecord) options.logRegistry.append(logRecord, "stderr", line);
70
+ },
71
+ }),
55
72
  ];
56
73
  lifecycle.registerService(config, child, cwd, () => {
57
74
  killChildProcess(child, "SIGTERM");
@@ -23,14 +23,15 @@ const MODULE_RUNNER_ENTRY = path.join(
23
23
  "template-step-module-runner.mjs"
24
24
  );
25
25
 
26
- export async function runConfiguredSteps({ config, steps = [], env, labelPrefix }) {
26
+ export async function runConfiguredSteps({ config, steps = [], env, labelPrefix, reporter = null }) {
27
27
  if (steps.length === 0) return;
28
28
  const resolvedToolchain = await resolveConfiguredToolchain(config);
29
- await announceResolvedToolchain(config, resolvedToolchain);
29
+ await announceResolvedToolchain(config, resolvedToolchain, reporter);
30
30
 
31
31
  for (const [index, step] of steps.entries()) {
32
32
  const label = `${labelPrefix}:${config.name}:${index + 1}`;
33
- console.log(`\n── ${label} ──`);
33
+ if (reporter?.phaseStarted) reporter.phaseStarted(label);
34
+ else console.log(`\n── ${label} ──`);
34
35
  await runConfiguredStep(config, step, env, resolvedToolchain);
35
36
  }
36
37
  }
@@ -23,10 +23,10 @@ export async function runWorker(
23
23
  lifecycle,
24
24
  claimNextTask,
25
25
  recordTaskOutcome,
26
- recordGraphError
26
+ recordGraphError,
27
+ reporter = null
27
28
  ) {
28
29
  const startedAt = Date.now();
29
- console.log(`\n══ worker ${worker.workerId} ══`);
30
30
  const errors = [];
31
31
 
32
32
  try {
@@ -69,8 +69,9 @@ export async function runWorker(
69
69
  }
70
70
  worker.currentGraphKey = task.graphKey;
71
71
  lease = await runtimeManager.acquire(task);
72
- const outcome = await runTask(lease.context, task, lifecycle, lease);
72
+ const outcome = await runTask(lease.context, task, lifecycle, lease, reporter);
73
73
  recordTaskOutcome(trackers, outcome.task, outcome);
74
+ reporter?.taskFinished?.(outcome.task, outcome);
74
75
  timingUpdates.push({
75
76
  key: outcome.task.timingKey,
76
77
  durationMs: outcome.durationMs,
@@ -100,20 +101,20 @@ export async function runWorker(
100
101
  };
101
102
  }
102
103
 
103
- async function runTask(context, task, lifecycle, lease) {
104
+ async function runTask(context, task, lifecycle, lease, reporter = null) {
104
105
  const targetConfig = context.configByName.get(task.targetName);
105
106
  if (!targetConfig) {
106
107
  throw new Error(`Runtime instance is missing target config "${task.targetName}"`);
107
108
  }
108
109
 
109
110
  if (task.framework === "playwright") {
110
- return runPlaywrightTask(targetConfig, task, lifecycle, lease);
111
+ return runPlaywrightTask(targetConfig, task, lifecycle, lease, reporter);
111
112
  }
112
113
  if (task.type === "dal") {
113
- return runDalTask(targetConfig, task, lifecycle, lease);
114
+ return runDalTask(targetConfig, task, lifecycle, lease, reporter);
114
115
  }
115
116
  if (task.framework === "k6" && HTTP_K6_TYPES.has(task.type)) {
116
- return runHttpK6Task(targetConfig, task, lifecycle, lease);
117
+ return runHttpK6Task(targetConfig, task, lifecycle, lease, reporter);
117
118
  }
118
119
 
119
120
  throw new Error(
@@ -84,12 +84,15 @@ export async function resolveConfiguredToolchain(config, options = {}) {
84
84
  }
85
85
  }
86
86
 
87
- export async function announceResolvedToolchain(config, resolvedToolchain) {
87
+ export async function announceResolvedToolchain(config, resolvedToolchain, reporter = null) {
88
88
  if (!resolvedToolchain || announcedToolchains.has(config)) return;
89
89
  announcedToolchains.add(config);
90
+ if (reporter?.toolchainResolved) {
91
+ reporter.toolchainResolved(config, resolvedToolchain);
92
+ return;
93
+ }
90
94
  console.log(
91
- `[testkit] ${config.runtimeLabel || config.name}:${config.name} toolchain ` +
92
- `${resolvedToolchain.summary}`
95
+ `[testkit] ${config.runtimeLabel || config.name}:${config.name} toolchain ${resolvedToolchain.summary}`
93
96
  );
94
97
  }
95
98
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
4
4
  "description": "CLI for discovering and running local HTTP, DAL, and Playwright test suites",
5
5
  "type": "module",
6
6
  "types": "./lib/index.d.ts",
@@ -26,6 +26,12 @@
26
26
  "bin": {
27
27
  "testkit": "bin/testkit.mjs"
28
28
  },
29
+ "oclif": {
30
+ "bin": "testkit",
31
+ "commands": "./lib/cli/commands",
32
+ "default": "run",
33
+ "topicSeparator": " "
34
+ },
29
35
  "scripts": {
30
36
  "test": "vitest run",
31
37
  "test:unit": "vitest run lib",
@@ -42,9 +48,11 @@
42
48
  "vitest": "^3.2.4"
43
49
  },
44
50
  "dependencies": {
45
- "cac": "^6.7.14",
51
+ "@oclif/core": "^4.10.6",
46
52
  "esbuild": "^0.25.11",
47
- "execa": "^9.5.0"
53
+ "execa": "^9.5.0",
54
+ "ink": "^7.0.1",
55
+ "react": "^19.2.5"
48
56
  },
49
57
  "engines": {
50
58
  "node": ">=18"