elasticdash-sdk 0.2.0
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/LICENSE +21 -0
- package/README.md +775 -0
- package/dist/browser-ui.d.ts +43 -0
- package/dist/browser-ui.d.ts.map +1 -0
- package/dist/browser-ui.js +246 -0
- package/dist/browser-ui.js.map +1 -0
- package/dist/capture/event.d.ts +33 -0
- package/dist/capture/event.d.ts.map +1 -0
- package/dist/capture/event.js +2 -0
- package/dist/capture/event.js.map +1 -0
- package/dist/capture/index.d.ts +4 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +4 -0
- package/dist/capture/index.js.map +1 -0
- package/dist/capture/recorder.d.ts +24 -0
- package/dist/capture/recorder.d.ts.map +1 -0
- package/dist/capture/recorder.js +46 -0
- package/dist/capture/recorder.js.map +1 -0
- package/dist/capture/replay.d.ts +20 -0
- package/dist/capture/replay.d.ts.map +1 -0
- package/dist/capture/replay.js +47 -0
- package/dist/capture/replay.js.map +1 -0
- package/dist/ci/api-client.d.ts +38 -0
- package/dist/ci/api-client.d.ts.map +1 -0
- package/dist/ci/api-client.js +96 -0
- package/dist/ci/api-client.js.map +1 -0
- package/dist/ci/benchmark.d.ts +33 -0
- package/dist/ci/benchmark.d.ts.map +1 -0
- package/dist/ci/benchmark.js +213 -0
- package/dist/ci/benchmark.js.map +1 -0
- package/dist/ci/ed-runner.d.ts +48 -0
- package/dist/ci/ed-runner.d.ts.map +1 -0
- package/dist/ci/ed-runner.js +260 -0
- package/dist/ci/ed-runner.js.map +1 -0
- package/dist/ci/executor.d.ts +13 -0
- package/dist/ci/executor.d.ts.map +1 -0
- package/dist/ci/executor.js +542 -0
- package/dist/ci/executor.js.map +1 -0
- package/dist/ci/git-info.d.ts +17 -0
- package/dist/ci/git-info.d.ts.map +1 -0
- package/dist/ci/git-info.js +102 -0
- package/dist/ci/git-info.js.map +1 -0
- package/dist/ci/index.d.ts +6 -0
- package/dist/ci/index.d.ts.map +1 -0
- package/dist/ci/index.js +4 -0
- package/dist/ci/index.js.map +1 -0
- package/dist/ci/measurement.d.ts +9 -0
- package/dist/ci/measurement.d.ts.map +1 -0
- package/dist/ci/measurement.js +15 -0
- package/dist/ci/measurement.js.map +1 -0
- package/dist/ci/replay.d.ts +31 -0
- package/dist/ci/replay.d.ts.map +1 -0
- package/dist/ci/replay.js +96 -0
- package/dist/ci/replay.js.map +1 -0
- package/dist/ci/reporters/default.d.ts +8 -0
- package/dist/ci/reporters/default.d.ts.map +1 -0
- package/dist/ci/reporters/default.js +46 -0
- package/dist/ci/reporters/default.js.map +1 -0
- package/dist/ci/reporters/index.d.ts +8 -0
- package/dist/ci/reporters/index.d.ts.map +1 -0
- package/dist/ci/reporters/index.js +14 -0
- package/dist/ci/reporters/index.js.map +1 -0
- package/dist/ci/reporters/json.d.ts +8 -0
- package/dist/ci/reporters/json.d.ts.map +1 -0
- package/dist/ci/reporters/json.js +14 -0
- package/dist/ci/reporters/json.js.map +1 -0
- package/dist/ci/reporters/junit.d.ts +8 -0
- package/dist/ci/reporters/junit.d.ts.map +1 -0
- package/dist/ci/reporters/junit.js +48 -0
- package/dist/ci/reporters/junit.js.map +1 -0
- package/dist/ci/runner.d.ts +3 -0
- package/dist/ci/runner.d.ts.map +1 -0
- package/dist/ci/runner.js +187 -0
- package/dist/ci/runner.js.map +1 -0
- package/dist/ci/test-discovery.d.ts +5 -0
- package/dist/ci/test-discovery.d.ts.map +1 -0
- package/dist/ci/test-discovery.js +11 -0
- package/dist/ci/test-discovery.js.map +1 -0
- package/dist/ci/test-loader.d.ts +19 -0
- package/dist/ci/test-loader.d.ts.map +1 -0
- package/dist/ci/test-loader.js +149 -0
- package/dist/ci/test-loader.js.map +1 -0
- package/dist/ci/test-registry.d.ts +42 -0
- package/dist/ci/test-registry.d.ts.map +1 -0
- package/dist/ci/test-registry.js +18 -0
- package/dist/ci/test-registry.js.map +1 -0
- package/dist/ci/trace-schema.d.ts +30 -0
- package/dist/ci/trace-schema.d.ts.map +1 -0
- package/dist/ci/trace-schema.js +66 -0
- package/dist/ci/trace-schema.js.map +1 -0
- package/dist/ci/trace-writer.d.ts +16 -0
- package/dist/ci/trace-writer.d.ts.map +1 -0
- package/dist/ci/trace-writer.js +108 -0
- package/dist/ci/trace-writer.js.map +1 -0
- package/dist/ci/types.d.ts +108 -0
- package/dist/ci/types.d.ts.map +1 -0
- package/dist/ci/types.js +3 -0
- package/dist/ci/types.js.map +1 -0
- package/dist/ci/upload-client.d.ts +74 -0
- package/dist/ci/upload-client.d.ts.map +1 -0
- package/dist/ci/upload-client.js +195 -0
- package/dist/ci/upload-client.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +716 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/agent-state.d.ts +47 -0
- package/dist/core/agent-state.d.ts.map +1 -0
- package/dist/core/agent-state.js +137 -0
- package/dist/core/agent-state.js.map +1 -0
- package/dist/core/judge-utils.d.ts +22 -0
- package/dist/core/judge-utils.d.ts.map +1 -0
- package/dist/core/judge-utils.js +211 -0
- package/dist/core/judge-utils.js.map +1 -0
- package/dist/core/registry.d.ts +28 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +52 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/dashboard-server.d.ts +65 -0
- package/dist/dashboard-server.d.ts.map +1 -0
- package/dist/dashboard-server.js +3940 -0
- package/dist/dashboard-server.js.map +1 -0
- package/dist/execution/tool-runner.d.ts +26 -0
- package/dist/execution/tool-runner.d.ts.map +1 -0
- package/dist/execution/tool-runner.js +316 -0
- package/dist/execution/tool-runner.js.map +1 -0
- package/dist/html/dashboard.html +2218 -0
- package/dist/http.d.ts +14 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +13 -0
- package/dist/http.js.map +1 -0
- package/dist/index.cjs +8102 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/ai-interceptor.d.ts +26 -0
- package/dist/interceptors/ai-interceptor.d.ts.map +1 -0
- package/dist/interceptors/ai-interceptor.js +756 -0
- package/dist/interceptors/ai-interceptor.js.map +1 -0
- package/dist/interceptors/db-auto.d.ts +8 -0
- package/dist/interceptors/db-auto.d.ts.map +1 -0
- package/dist/interceptors/db-auto.js +217 -0
- package/dist/interceptors/db-auto.js.map +1 -0
- package/dist/interceptors/db.d.ts +23 -0
- package/dist/interceptors/db.d.ts.map +1 -0
- package/dist/interceptors/db.js +137 -0
- package/dist/interceptors/db.js.map +1 -0
- package/dist/interceptors/http.d.ts +28 -0
- package/dist/interceptors/http.d.ts.map +1 -0
- package/dist/interceptors/http.js +356 -0
- package/dist/interceptors/http.js.map +1 -0
- package/dist/interceptors/side-effects.d.ts +7 -0
- package/dist/interceptors/side-effects.d.ts.map +1 -0
- package/dist/interceptors/side-effects.js +72 -0
- package/dist/interceptors/side-effects.js.map +1 -0
- package/dist/interceptors/telemetry-push.d.ts +142 -0
- package/dist/interceptors/telemetry-push.d.ts.map +1 -0
- package/dist/interceptors/telemetry-push.js +463 -0
- package/dist/interceptors/telemetry-push.js.map +1 -0
- package/dist/interceptors/tool.d.ts +2 -0
- package/dist/interceptors/tool.d.ts.map +1 -0
- package/dist/interceptors/tool.js +274 -0
- package/dist/interceptors/tool.js.map +1 -0
- package/dist/interceptors/workflow-ai.d.ts +5 -0
- package/dist/interceptors/workflow-ai.d.ts.map +1 -0
- package/dist/interceptors/workflow-ai.js +382 -0
- package/dist/interceptors/workflow-ai.js.map +1 -0
- package/dist/internals/conditional-recorder.d.ts +21 -0
- package/dist/internals/conditional-recorder.d.ts.map +1 -0
- package/dist/internals/conditional-recorder.js +54 -0
- package/dist/internals/conditional-recorder.js.map +1 -0
- package/dist/internals/mock-resolver.d.ts +146 -0
- package/dist/internals/mock-resolver.d.ts.map +1 -0
- package/dist/internals/mock-resolver.js +427 -0
- package/dist/internals/mock-resolver.js.map +1 -0
- package/dist/matchers/index.d.ts +96 -0
- package/dist/matchers/index.d.ts.map +1 -0
- package/dist/matchers/index.js +668 -0
- package/dist/matchers/index.js.map +1 -0
- package/dist/observability.d.ts +82 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +471 -0
- package/dist/observability.js.map +1 -0
- package/dist/portal-executor.d.ts +30 -0
- package/dist/portal-executor.d.ts.map +1 -0
- package/dist/portal-executor.js +324 -0
- package/dist/portal-executor.js.map +1 -0
- package/dist/portal-server.d.ts +3 -0
- package/dist/portal-server.d.ts.map +1 -0
- package/dist/portal-server.js +279 -0
- package/dist/portal-server.js.map +1 -0
- package/dist/proxy/llm-capture.d.ts +14 -0
- package/dist/proxy/llm-capture.d.ts.map +1 -0
- package/dist/proxy/llm-capture.js +264 -0
- package/dist/proxy/llm-capture.js.map +1 -0
- package/dist/reporter.d.ts +3 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +72 -0
- package/dist/reporter.js.map +1 -0
- package/dist/runWorkflowSubprocess.d.ts +14 -0
- package/dist/runWorkflowSubprocess.d.ts.map +1 -0
- package/dist/runWorkflowSubprocess.js +66 -0
- package/dist/runWorkflowSubprocess.js.map +1 -0
- package/dist/runner.d.ts +16 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +138 -0
- package/dist/runner.js.map +1 -0
- package/dist/socket-connector.d.ts +22 -0
- package/dist/socket-connector.d.ts.map +1 -0
- package/dist/socket-connector.js +104 -0
- package/dist/socket-connector.js.map +1 -0
- package/dist/telemetry-batcher.d.ts +56 -0
- package/dist/telemetry-batcher.d.ts.map +1 -0
- package/dist/telemetry-batcher.js +143 -0
- package/dist/telemetry-batcher.js.map +1 -0
- package/dist/test-setup.d.ts +12 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +13 -0
- package/dist/test-setup.js.map +1 -0
- package/dist/tool-registry.d.ts +31 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +73 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/tool-runner-worker.d.ts +2 -0
- package/dist/tool-runner-worker.d.ts.map +1 -0
- package/dist/tool-runner-worker.js +215 -0
- package/dist/tool-runner-worker.js.map +1 -0
- package/dist/trace-adapter/context.d.ts +72 -0
- package/dist/trace-adapter/context.d.ts.map +1 -0
- package/dist/trace-adapter/context.js +80 -0
- package/dist/trace-adapter/context.js.map +1 -0
- package/dist/tracing.d.ts +2 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +59 -0
- package/dist/tracing.js.map +1 -0
- package/dist/trigger-executor.d.ts +12 -0
- package/dist/trigger-executor.d.ts.map +1 -0
- package/dist/trigger-executor.js +130 -0
- package/dist/trigger-executor.js.map +1 -0
- package/dist/types/portal.d.ts +76 -0
- package/dist/types/portal.d.ts.map +1 -0
- package/dist/types/portal.js +2 -0
- package/dist/types/portal.js.map +1 -0
- package/dist/utils/debug.d.ts +3 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +8 -0
- package/dist/utils/debug.js.map +1 -0
- package/dist/utils/license-error.d.ts +23 -0
- package/dist/utils/license-error.d.ts.map +1 -0
- package/dist/utils/license-error.js +42 -0
- package/dist/utils/license-error.js.map +1 -0
- package/dist/utils/redact.d.ts +7 -0
- package/dist/utils/redact.d.ts.map +1 -0
- package/dist/utils/redact.js +26 -0
- package/dist/utils/redact.js.map +1 -0
- package/dist/workflow-runner-worker.d.ts +2 -0
- package/dist/workflow-runner-worker.d.ts.map +1 -0
- package/dist/workflow-runner-worker.js +329 -0
- package/dist/workflow-runner-worker.js.map +1 -0
- package/dist/workflow-runner.d.ts +14 -0
- package/dist/workflow-runner.d.ts.map +1 -0
- package/dist/workflow-runner.js +34 -0
- package/dist/workflow-runner.js.map +1 -0
- package/docs/agent-coding-instructions.md +138 -0
- package/docs/agent-integration-guide.md +564 -0
- package/docs/agents.md +140 -0
- package/docs/dashboard.md +394 -0
- package/docs/deno.md +69 -0
- package/docs/instrumentation.md +424 -0
- package/docs/langfuse-trace-structure.md +145 -0
- package/docs/matchers.md +173 -0
- package/docs/observability_contract.md +192 -0
- package/docs/observability_mode.md +195 -0
- package/docs/quickstart.md +621 -0
- package/docs/security-compliance.md +566 -0
- package/docs/test-writing-guidelines.md +444 -0
- package/docs/tools.md +165 -0
- package/docs/workflow-modes.md +253 -0
- package/package.json +76 -0
- package/src/browser-ui.ts +281 -0
- package/src/capture/event.ts +30 -0
- package/src/capture/index.ts +3 -0
- package/src/capture/recorder.ts +62 -0
- package/src/capture/replay.ts +55 -0
- package/src/ci/api-client.ts +136 -0
- package/src/ci/benchmark.ts +257 -0
- package/src/ci/ed-runner.ts +351 -0
- package/src/ci/executor.ts +671 -0
- package/src/ci/git-info.ts +127 -0
- package/src/ci/index.ts +5 -0
- package/src/ci/measurement.ts +25 -0
- package/src/ci/replay.ts +127 -0
- package/src/ci/reporters/default.ts +50 -0
- package/src/ci/reporters/index.ts +21 -0
- package/src/ci/reporters/json.ts +18 -0
- package/src/ci/reporters/junit.ts +61 -0
- package/src/ci/runner.ts +208 -0
- package/src/ci/test-discovery.ts +16 -0
- package/src/ci/test-loader.ts +187 -0
- package/src/ci/test-registry.ts +62 -0
- package/src/ci/trace-schema.ts +96 -0
- package/src/ci/trace-writer.ts +107 -0
- package/src/ci/types.ts +115 -0
- package/src/ci/upload-client.ts +300 -0
- package/src/cli.ts +811 -0
- package/src/core/agent-state.ts +162 -0
- package/src/core/judge-utils.ts +232 -0
- package/src/core/registry.ts +92 -0
- package/src/dashboard-server.ts +2047 -0
- package/src/execution/tool-runner.ts +352 -0
- package/src/html/dashboard.html +2218 -0
- package/src/http.ts +13 -0
- package/src/index.ts +138 -0
- package/src/interceptors/ai-interceptor.ts +798 -0
- package/src/interceptors/db-auto.ts +243 -0
- package/src/interceptors/db.ts +156 -0
- package/src/interceptors/http.ts +393 -0
- package/src/interceptors/side-effects.ts +83 -0
- package/src/interceptors/telemetry-push.ts +537 -0
- package/src/interceptors/tool.ts +287 -0
- package/src/interceptors/workflow-ai.ts +419 -0
- package/src/internals/conditional-recorder.ts +63 -0
- package/src/internals/mock-resolver.ts +492 -0
- package/src/matchers/index.ts +824 -0
- package/src/observability.ts +501 -0
- package/src/portal-executor.ts +355 -0
- package/src/portal-server.ts +304 -0
- package/src/proxy/llm-capture.ts +301 -0
- package/src/reporter.ts +81 -0
- package/src/runWorkflowSubprocess.ts +74 -0
- package/src/runner.ts +178 -0
- package/src/socket-connector.ts +117 -0
- package/src/telemetry-batcher.ts +191 -0
- package/src/test-setup.ts +16 -0
- package/src/tool-registry.ts +94 -0
- package/src/tool-runner-worker.ts +244 -0
- package/src/trace-adapter/context.ts +156 -0
- package/src/tracing.ts +62 -0
- package/src/trigger-executor.ts +171 -0
- package/src/types/agent.d.ts +63 -0
- package/src/types/expect.d.ts +81 -0
- package/src/types/modules.d.ts +2 -0
- package/src/types/portal.ts +69 -0
- package/src/utils/debug.ts +8 -0
- package/src/utils/license-error.ts +43 -0
- package/src/utils/redact.ts +25 -0
- package/src/workflow-runner-worker.ts +386 -0
- package/src/workflow-runner.ts +58 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
// ─── CI Environment Auto-Detection ──────────────────────────
|
|
4
|
+
|
|
5
|
+
export interface GitInfo {
|
|
6
|
+
branch?: string
|
|
7
|
+
commit?: string
|
|
8
|
+
commitMessage?: string
|
|
9
|
+
baseBranch?: string
|
|
10
|
+
prNumber?: number
|
|
11
|
+
prUrl?: string
|
|
12
|
+
repo?: string
|
|
13
|
+
ciProvider?: string
|
|
14
|
+
ciRunUrl?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Auto-detect git info from CI environment variables.
|
|
19
|
+
* Supports GitHub Actions, GitLab CI, CircleCI, Buildkite, and local git.
|
|
20
|
+
*/
|
|
21
|
+
export function detectGitInfo(): GitInfo {
|
|
22
|
+
const env = process.env
|
|
23
|
+
|
|
24
|
+
// GitHub Actions
|
|
25
|
+
if (env.GITHUB_ACTIONS === 'true') {
|
|
26
|
+
const prNumber = env.GITHUB_EVENT_NAME === 'pull_request'
|
|
27
|
+
? parseInt(env.GITHUB_REF?.match(/refs\/pull\/(\d+)/)?.[1] ?? '', 10) || undefined
|
|
28
|
+
: undefined
|
|
29
|
+
|
|
30
|
+
const repo = env.GITHUB_REPOSITORY
|
|
31
|
+
const prUrl = prNumber && repo
|
|
32
|
+
? `https://github.com/${repo}/pull/${prNumber}`
|
|
33
|
+
: undefined
|
|
34
|
+
|
|
35
|
+
const serverUrl = env.GITHUB_SERVER_URL || 'https://github.com'
|
|
36
|
+
const ciRunUrl = repo && env.GITHUB_RUN_ID
|
|
37
|
+
? `${serverUrl}/${repo}/actions/runs/${env.GITHUB_RUN_ID}`
|
|
38
|
+
: undefined
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
branch: env.GITHUB_HEAD_REF || env.GITHUB_REF_NAME,
|
|
42
|
+
commit: env.GITHUB_SHA,
|
|
43
|
+
commitMessage: env.GITHUB_COMMIT_MESSAGE,
|
|
44
|
+
baseBranch: env.GITHUB_BASE_REF || undefined,
|
|
45
|
+
prNumber,
|
|
46
|
+
prUrl,
|
|
47
|
+
repo,
|
|
48
|
+
ciProvider: 'github-actions',
|
|
49
|
+
ciRunUrl,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// GitLab CI
|
|
54
|
+
if (env.GITLAB_CI === 'true') {
|
|
55
|
+
const prNumber = env.CI_MERGE_REQUEST_IID
|
|
56
|
+
? parseInt(env.CI_MERGE_REQUEST_IID, 10) || undefined
|
|
57
|
+
: undefined
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
branch: env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME || env.CI_COMMIT_BRANCH,
|
|
61
|
+
commit: env.CI_COMMIT_SHA,
|
|
62
|
+
commitMessage: env.CI_COMMIT_MESSAGE,
|
|
63
|
+
baseBranch: env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME || undefined,
|
|
64
|
+
prNumber,
|
|
65
|
+
prUrl: env.CI_MERGE_REQUEST_PROJECT_URL && prNumber
|
|
66
|
+
? `${env.CI_MERGE_REQUEST_PROJECT_URL}/-/merge_requests/${prNumber}`
|
|
67
|
+
: undefined,
|
|
68
|
+
repo: env.CI_PROJECT_PATH,
|
|
69
|
+
ciProvider: 'gitlab-ci',
|
|
70
|
+
ciRunUrl: env.CI_JOB_URL,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// CircleCI
|
|
75
|
+
if (env.CIRCLECI === 'true') {
|
|
76
|
+
const prNumber = env.CIRCLE_PULL_REQUEST
|
|
77
|
+
? parseInt(env.CIRCLE_PULL_REQUEST.split('/').pop() ?? '', 10) || undefined
|
|
78
|
+
: undefined
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
branch: env.CIRCLE_BRANCH,
|
|
82
|
+
commit: env.CIRCLE_SHA1,
|
|
83
|
+
prNumber,
|
|
84
|
+
repo: env.CIRCLE_PROJECT_REPONAME,
|
|
85
|
+
ciProvider: 'circleci',
|
|
86
|
+
ciRunUrl: env.CIRCLE_BUILD_URL,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Buildkite
|
|
91
|
+
if (env.BUILDKITE === 'true') {
|
|
92
|
+
const prNumber = env.BUILDKITE_PULL_REQUEST && env.BUILDKITE_PULL_REQUEST !== 'false'
|
|
93
|
+
? parseInt(env.BUILDKITE_PULL_REQUEST, 10) || undefined
|
|
94
|
+
: undefined
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
branch: env.BUILDKITE_BRANCH,
|
|
98
|
+
commit: env.BUILDKITE_COMMIT,
|
|
99
|
+
baseBranch: env.BUILDKITE_PULL_REQUEST_BASE_BRANCH || undefined,
|
|
100
|
+
prNumber,
|
|
101
|
+
repo: env.BUILDKITE_REPO,
|
|
102
|
+
ciProvider: 'buildkite',
|
|
103
|
+
ciRunUrl: env.BUILDKITE_BUILD_URL,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Local fallback: shell out to git
|
|
108
|
+
return detectLocalGitInfo()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function detectLocalGitInfo(): GitInfo {
|
|
112
|
+
try {
|
|
113
|
+
const commit = execSync('git rev-parse HEAD', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
|
|
114
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
|
|
115
|
+
return {
|
|
116
|
+
branch: branch || undefined,
|
|
117
|
+
commit: commit || undefined,
|
|
118
|
+
ciProvider: 'local',
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
return {
|
|
122
|
+
branch: 'unknown',
|
|
123
|
+
commit: 'unknown',
|
|
124
|
+
ciProvider: 'local',
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
package/src/ci/index.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { runCI } from './runner.js'
|
|
2
|
+
export { fetchTestGroups, submitTestRun, createBatch } from './api-client.js'
|
|
3
|
+
export { detectGitInfo } from './git-info.js'
|
|
4
|
+
export type { CIRunConfig, CIRunSummary, CITestResult, CISingleRunResult, CIExpectationResult } from './types.js'
|
|
5
|
+
export type { GitInfo } from './git-info.js'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { EdReplayContext } from './replay.js'
|
|
2
|
+
|
|
3
|
+
export interface TestMeasurement {
|
|
4
|
+
duration_ms: number
|
|
5
|
+
tokens_input?: number
|
|
6
|
+
tokens_output?: number
|
|
7
|
+
tokens_total?: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function collectMeasurement(ctx: EdReplayContext): TestMeasurement | null {
|
|
11
|
+
const m = ctx.targetMeasurement
|
|
12
|
+
if (!m) return null
|
|
13
|
+
|
|
14
|
+
const result: TestMeasurement = {
|
|
15
|
+
duration_ms: m.duration_ms,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (m.tokens) {
|
|
19
|
+
result.tokens_input = m.tokens.input
|
|
20
|
+
result.tokens_output = m.tokens.output
|
|
21
|
+
result.tokens_total = m.tokens.total
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return result
|
|
25
|
+
}
|
package/src/ci/replay.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
3
|
+
import type { DiskTrace, DiskTraceStep } from './trace-schema.js'
|
|
4
|
+
|
|
5
|
+
// ─── Types ──────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export interface ReplayMeasurement {
|
|
8
|
+
duration_ms: number
|
|
9
|
+
tokens?: { input: number; output: number; total: number } | null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface EdReplayContext {
|
|
13
|
+
steps: DiskTraceStep[]
|
|
14
|
+
consumed: boolean[]
|
|
15
|
+
targetStepId: string
|
|
16
|
+
targetMeasurement: ReplayMeasurement | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ReplayMissError extends Error {
|
|
20
|
+
constructor(public callType: string, public callName: string) {
|
|
21
|
+
super(`replay miss: ${callType}::${callName}`)
|
|
22
|
+
this.name = 'ReplayMissError'
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── ALS-backed context ─────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const g = globalThis as Record<string, unknown>
|
|
29
|
+
const ED_REPLAY_ALS_KEY = '__elasticdash_ed_replay_als__'
|
|
30
|
+
const edReplayAls: AsyncLocalStorage<EdReplayContext | undefined> =
|
|
31
|
+
(g[ED_REPLAY_ALS_KEY] as AsyncLocalStorage<EdReplayContext | undefined>) ??
|
|
32
|
+
new AsyncLocalStorage<EdReplayContext | undefined>()
|
|
33
|
+
if (!g[ED_REPLAY_ALS_KEY]) g[ED_REPLAY_ALS_KEY] = edReplayAls
|
|
34
|
+
|
|
35
|
+
export function getEdReplayContext(): EdReplayContext | undefined {
|
|
36
|
+
return edReplayAls.getStore()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function installReplay(ctx: EdReplayContext): void {
|
|
40
|
+
edReplayAls.enterWith(ctx)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function uninstallReplay(): void {
|
|
44
|
+
edReplayAls.enterWith(undefined)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Context creation ───────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
export function createReplayContext(trace: DiskTrace, targetStepId: string): EdReplayContext {
|
|
50
|
+
return {
|
|
51
|
+
steps: trace.steps,
|
|
52
|
+
consumed: new Array(trace.steps.length).fill(false),
|
|
53
|
+
targetStepId,
|
|
54
|
+
targetMeasurement: null,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─── Input canonicalization & match key ─────────────────────
|
|
59
|
+
|
|
60
|
+
export function canonicalizeInput(input: unknown): string {
|
|
61
|
+
return JSON.stringify(sortKeys(input))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sortKeys(value: unknown): unknown {
|
|
65
|
+
if (value === null || value === undefined) return value
|
|
66
|
+
if (Array.isArray(value)) return value.map(sortKeys)
|
|
67
|
+
if (typeof value === 'number') {
|
|
68
|
+
// Round floats to 6 decimal places
|
|
69
|
+
if (!Number.isInteger(value)) {
|
|
70
|
+
return Math.round(value * 1e6) / 1e6
|
|
71
|
+
}
|
|
72
|
+
return value
|
|
73
|
+
}
|
|
74
|
+
if (typeof value === 'string') {
|
|
75
|
+
// Normalize whitespace
|
|
76
|
+
return value.replace(/\s+/g, ' ').trim()
|
|
77
|
+
}
|
|
78
|
+
if (typeof value === 'object') {
|
|
79
|
+
const sorted: Record<string, unknown> = {}
|
|
80
|
+
for (const key of Object.keys(value as Record<string, unknown>).sort()) {
|
|
81
|
+
sorted[key] = sortKeys((value as Record<string, unknown>)[key])
|
|
82
|
+
}
|
|
83
|
+
return sorted
|
|
84
|
+
}
|
|
85
|
+
return value
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function computeMatchKey(type: 'tool_call' | 'ai_call', name: string, input: unknown): string {
|
|
89
|
+
const canonical = canonicalizeInput(input)
|
|
90
|
+
const hash = createHash('sha256').update(canonical).digest('hex').slice(0, 16)
|
|
91
|
+
return `${type}::${name}::${hash}`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Replay call ────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
export function replayCall(
|
|
97
|
+
ctx: EdReplayContext,
|
|
98
|
+
type: 'tool_call' | 'ai_call',
|
|
99
|
+
name: string,
|
|
100
|
+
input: unknown,
|
|
101
|
+
): { output: unknown; measurement: ReplayMeasurement | null } {
|
|
102
|
+
const runtimeKey = computeMatchKey(type, name, input)
|
|
103
|
+
|
|
104
|
+
// Find next unconsumed step with matching key
|
|
105
|
+
for (let i = 0; i < ctx.steps.length; i++) {
|
|
106
|
+
if (ctx.consumed[i]) continue
|
|
107
|
+
const step = ctx.steps[i]
|
|
108
|
+
const stepKey = computeMatchKey(step.type, step.name, step.input)
|
|
109
|
+
if (stepKey === runtimeKey) {
|
|
110
|
+
ctx.consumed[i] = true
|
|
111
|
+
|
|
112
|
+
const measurement: ReplayMeasurement = {
|
|
113
|
+
duration_ms: step.duration_ms,
|
|
114
|
+
tokens: step.tokens ?? null,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Capture measurement if this is the target step
|
|
118
|
+
if (step.step_id === ctx.targetStepId) {
|
|
119
|
+
ctx.targetMeasurement = measurement
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { output: step.output, measurement }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
throw new ReplayMissError(type, name)
|
|
127
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import type { Reporter } from './index.js'
|
|
3
|
+
import type { EdTestResult, EdTestRunResult } from '../ed-runner.js'
|
|
4
|
+
|
|
5
|
+
export class DefaultReporter implements Reporter {
|
|
6
|
+
onTestStart(name: string): void {
|
|
7
|
+
process.stdout.write(chalk.gray(` ${name} ... `))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
onTestResult(result: EdTestResult): void {
|
|
11
|
+
if (result.status === 'pass') {
|
|
12
|
+
console.log(chalk.green('PASS'))
|
|
13
|
+
if (result.benchmarkResult) {
|
|
14
|
+
for (const m of result.benchmarkResult.metrics) {
|
|
15
|
+
console.log(chalk.gray(` ${m.name}: ${m.value} (threshold: ${m.threshold}) `) + chalk.green('✓'))
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
console.log(chalk.red('FAIL'))
|
|
20
|
+
if (result.benchmarkResult) {
|
|
21
|
+
for (const m of result.benchmarkResult.metrics) {
|
|
22
|
+
const icon = m.passed ? chalk.green('✓') : chalk.red('✗')
|
|
23
|
+
console.log(chalk.gray(` ${m.name}: ${m.value} (threshold: ${m.threshold}) `) + icon)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (result.failureReason) {
|
|
27
|
+
console.log(chalk.red(` → ${result.failureReason}`))
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
console.log()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
onRunComplete(runResult: EdTestRunResult, uploadUrl?: string): void {
|
|
34
|
+
const passed = runResult.results.filter(r => r.status === 'pass').length
|
|
35
|
+
const failed = runResult.results.filter(r => r.status === 'fail').length
|
|
36
|
+
const total = runResult.results.length
|
|
37
|
+
|
|
38
|
+
console.log(chalk.white.bold('─'.repeat(45)))
|
|
39
|
+
|
|
40
|
+
const parts: string[] = []
|
|
41
|
+
if (passed > 0) parts.push(chalk.green(`${passed} passed`))
|
|
42
|
+
if (failed > 0) parts.push(chalk.red(`${failed} failed`))
|
|
43
|
+
console.log(`${parts.join(', ')}, ${total} total`)
|
|
44
|
+
|
|
45
|
+
if (uploadUrl) {
|
|
46
|
+
console.log(chalk.gray(`Uploaded to ${uploadUrl}`))
|
|
47
|
+
}
|
|
48
|
+
console.log()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { EdTestResult, EdTestRunResult } from '../ed-runner.js'
|
|
2
|
+
import { DefaultReporter } from './default.js'
|
|
3
|
+
import { JsonReporter } from './json.js'
|
|
4
|
+
import { JunitReporter } from './junit.js'
|
|
5
|
+
|
|
6
|
+
export interface Reporter {
|
|
7
|
+
onTestStart(name: string): void
|
|
8
|
+
onTestResult(result: EdTestResult): void
|
|
9
|
+
onRunComplete(runResult: EdTestRunResult, uploadUrl?: string): void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createReporter(name: 'default' | 'json' | 'junit'): Reporter {
|
|
13
|
+
switch (name) {
|
|
14
|
+
case 'json':
|
|
15
|
+
return new JsonReporter()
|
|
16
|
+
case 'junit':
|
|
17
|
+
return new JunitReporter()
|
|
18
|
+
default:
|
|
19
|
+
return new DefaultReporter()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Reporter } from './index.js'
|
|
2
|
+
import type { EdTestResult, EdTestRunResult } from '../ed-runner.js'
|
|
3
|
+
import { buildUploadPayload } from '../upload-client.js'
|
|
4
|
+
|
|
5
|
+
export class JsonReporter implements Reporter {
|
|
6
|
+
onTestStart(_name: string): void {
|
|
7
|
+
// silent
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
onTestResult(_result: EdTestResult): void {
|
|
11
|
+
// silent — output everything at the end
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
onRunComplete(runResult: EdTestRunResult, _uploadUrl?: string): void {
|
|
15
|
+
const payload = buildUploadPayload(runResult)
|
|
16
|
+
console.log(JSON.stringify(payload, null, 2))
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Reporter } from './index.js'
|
|
2
|
+
import type { EdTestResult, EdTestRunResult } from '../ed-runner.js'
|
|
3
|
+
|
|
4
|
+
function escapeXml(str: string): string {
|
|
5
|
+
return str
|
|
6
|
+
.replace(/&/g, '&')
|
|
7
|
+
.replace(/</g, '<')
|
|
8
|
+
.replace(/>/g, '>')
|
|
9
|
+
.replace(/"/g, '"')
|
|
10
|
+
.replace(/'/g, ''')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class JunitReporter implements Reporter {
|
|
14
|
+
onTestStart(_name: string): void {
|
|
15
|
+
// silent
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
onTestResult(_result: EdTestResult): void {
|
|
19
|
+
// silent — output everything at the end
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
onRunComplete(runResult: EdTestRunResult, _uploadUrl?: string): void {
|
|
23
|
+
const results = runResult.results
|
|
24
|
+
const passed = results.filter(r => r.status === 'pass').length
|
|
25
|
+
const failed = results.filter(r => r.status === 'fail').length
|
|
26
|
+
const totalTime = results.reduce((sum, r) => sum + r.durationMs, 0) / 1000
|
|
27
|
+
|
|
28
|
+
const lines: string[] = [
|
|
29
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
30
|
+
`<testsuites tests="${results.length}" failures="${failed}" errors="0" time="${totalTime.toFixed(3)}">`,
|
|
31
|
+
` <testsuite name="ed_tests" tests="${results.length}" failures="${failed}" errors="0" time="${totalTime.toFixed(3)}">`,
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
for (const r of results) {
|
|
35
|
+
const time = (r.durationMs / 1000).toFixed(3)
|
|
36
|
+
// Use duration_ms measurement as the duration attribute if available
|
|
37
|
+
const metricTime = r.measurement ? (r.measurement.duration_ms / 1000).toFixed(3) : time
|
|
38
|
+
|
|
39
|
+
lines.push(` <testcase name="${escapeXml(r.testName)}" time="${metricTime}">`)
|
|
40
|
+
|
|
41
|
+
if (r.status === 'fail') {
|
|
42
|
+
const isReplayMiss = r.failureReason?.startsWith('replay miss:')
|
|
43
|
+
const isTimeout = r.failureReason?.startsWith('test timed out')
|
|
44
|
+
const isExecError = r.failureReason?.startsWith('execution error:')
|
|
45
|
+
const type = isReplayMiss ? 'ReplayMissError'
|
|
46
|
+
: isTimeout ? 'TimeoutError'
|
|
47
|
+
: isExecError ? 'ExecutionError'
|
|
48
|
+
: 'BenchmarkFailure'
|
|
49
|
+
|
|
50
|
+
lines.push(` <failure type="${type}" message="${escapeXml(r.failureReason ?? 'test failed')}">${escapeXml(r.failureReason ?? '')}</failure>`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
lines.push(' </testcase>')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
lines.push(' </testsuite>')
|
|
57
|
+
lines.push('</testsuites>')
|
|
58
|
+
|
|
59
|
+
console.log(lines.join('\n'))
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/ci/runner.ts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { fetchTestGroups, submitTestRun, createBatch } from './api-client.js'
|
|
3
|
+
import { executeTest } from './executor.js'
|
|
4
|
+
import { detectGitInfo } from './git-info.js'
|
|
5
|
+
import type { CIRunConfig, CIRunSummary, CITestResult } from './types.js'
|
|
6
|
+
|
|
7
|
+
// ─── CI Runner ───────────────────────────────────────────────
|
|
8
|
+
// Orchestrates the full CI/CD test flow:
|
|
9
|
+
// 1. Fetch test groups from backend
|
|
10
|
+
// 2. Execute each test
|
|
11
|
+
// 3. Submit results
|
|
12
|
+
// 4. Create batch
|
|
13
|
+
// 5. Print summary
|
|
14
|
+
|
|
15
|
+
export async function runCI(config: CIRunConfig): Promise<CIRunSummary> {
|
|
16
|
+
const { serverUrl, apiKey } = config
|
|
17
|
+
const cwd = process.cwd()
|
|
18
|
+
|
|
19
|
+
// Debug: log config so CI logs show what was received
|
|
20
|
+
console.log(`[elasticdash ci] serverUrl: ${serverUrl || '(not set)'}`)
|
|
21
|
+
console.log(`[elasticdash ci] apiKey: ${apiKey ? apiKey.substring(0, 7) + '...' + apiKey.substring(apiKey.length - 4) : '(not set)'}`)
|
|
22
|
+
|
|
23
|
+
// Merge explicit git info with auto-detected CI env
|
|
24
|
+
const detected = detectGitInfo()
|
|
25
|
+
const gitBranch = config.gitBranch || detected.branch
|
|
26
|
+
const gitCommit = config.gitCommit || detected.commit
|
|
27
|
+
const gitCommitMessage = config.gitCommitMessage || detected.commitMessage
|
|
28
|
+
const gitPrNumber = config.gitPrNumber || detected.prNumber
|
|
29
|
+
const gitPrUrl = config.gitPrUrl || detected.prUrl
|
|
30
|
+
const triggeredBy = config.triggeredBy || 'ci'
|
|
31
|
+
|
|
32
|
+
const overallStart = Date.now()
|
|
33
|
+
|
|
34
|
+
// Step 1: Fetch test groups
|
|
35
|
+
console.log(chalk.cyan('\n[elasticdash ci] Fetching test groups...'))
|
|
36
|
+
|
|
37
|
+
const testGroups = await fetchTestGroups(serverUrl, apiKey, {
|
|
38
|
+
workflowName: config.workflowName,
|
|
39
|
+
tags: config.tags,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
if (testGroups.length === 0) {
|
|
43
|
+
console.log(chalk.yellow('[elasticdash ci] No active test groups found.'))
|
|
44
|
+
return {
|
|
45
|
+
total: 0, passed: 0, failed: 0, skipped: 0,
|
|
46
|
+
durationMs: Date.now() - overallStart,
|
|
47
|
+
batchId: null,
|
|
48
|
+
results: [],
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const totalTests = testGroups.reduce((sum, g) => sum + g.tests.length, 0)
|
|
53
|
+
console.log(chalk.cyan(`[elasticdash ci] Found ${testGroups.length} test group(s), ${totalTests} test(s) total.\n`))
|
|
54
|
+
|
|
55
|
+
// Step 2 & 3: Execute and submit
|
|
56
|
+
const allResults: CITestResult[] = []
|
|
57
|
+
const runIds: number[] = []
|
|
58
|
+
|
|
59
|
+
for (const group of testGroups) {
|
|
60
|
+
console.log(chalk.white.bold(` ${group.name}`) + chalk.gray(` (${group.tests.length} tests)`))
|
|
61
|
+
|
|
62
|
+
for (const test of group.tests) {
|
|
63
|
+
const testLabel = test.name || `${test.test_type}:${test.target_step_name || 'unnamed'}`
|
|
64
|
+
process.stdout.write(chalk.gray(` ${testLabel} ... `))
|
|
65
|
+
|
|
66
|
+
const testStart = Date.now()
|
|
67
|
+
let testResult: CITestResult
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const execution = await executeTest(test, cwd)
|
|
71
|
+
|
|
72
|
+
// Submit result to backend
|
|
73
|
+
const payload = {
|
|
74
|
+
testGroupTestId: test.id,
|
|
75
|
+
triggeredBy,
|
|
76
|
+
passed: execution.passed,
|
|
77
|
+
summary: execution.passed ? 'Passed' : 'Failed',
|
|
78
|
+
gitBranch, gitCommit, gitCommitMessage, gitPrNumber, gitPrUrl,
|
|
79
|
+
singleRuns: execution.singleRuns,
|
|
80
|
+
expectationResults: execution.expectationResults,
|
|
81
|
+
startedAt: new Date(testStart).toISOString(),
|
|
82
|
+
completedAt: new Date().toISOString(),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let runId: number | null = null
|
|
86
|
+
try {
|
|
87
|
+
const submitted = await submitTestRun(serverUrl, apiKey, group.id, payload)
|
|
88
|
+
runId = submitted.id
|
|
89
|
+
runIds.push(runId)
|
|
90
|
+
} catch (submitErr) {
|
|
91
|
+
// Non-fatal: log but don't fail the test
|
|
92
|
+
console.error(chalk.yellow(`\n [warn] Failed to submit result: ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
testResult = {
|
|
96
|
+
testGroupId: group.id,
|
|
97
|
+
testGroupName: group.name,
|
|
98
|
+
testId: test.id,
|
|
99
|
+
testName: test.name,
|
|
100
|
+
testType: test.test_type,
|
|
101
|
+
passed: execution.passed,
|
|
102
|
+
runId,
|
|
103
|
+
singleRuns: execution.singleRuns,
|
|
104
|
+
expectationResults: execution.expectationResults,
|
|
105
|
+
durationMs: execution.durationMs,
|
|
106
|
+
}
|
|
107
|
+
} catch (err) {
|
|
108
|
+
testResult = {
|
|
109
|
+
testGroupId: group.id,
|
|
110
|
+
testGroupName: group.name,
|
|
111
|
+
testId: test.id,
|
|
112
|
+
testName: test.name,
|
|
113
|
+
testType: test.test_type,
|
|
114
|
+
passed: false,
|
|
115
|
+
runId: null,
|
|
116
|
+
singleRuns: [],
|
|
117
|
+
expectationResults: [],
|
|
118
|
+
error: err instanceof Error ? err.message : String(err),
|
|
119
|
+
durationMs: Date.now() - testStart,
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
allResults.push(testResult)
|
|
124
|
+
|
|
125
|
+
// Print result
|
|
126
|
+
const durationStr = chalk.gray(`(${testResult.durationMs}ms)`)
|
|
127
|
+
if (testResult.passed) {
|
|
128
|
+
console.log(chalk.green('PASS') + ` ${durationStr}`)
|
|
129
|
+
} else {
|
|
130
|
+
console.log(chalk.red('FAIL') + ` ${durationStr}`)
|
|
131
|
+
if (testResult.error) {
|
|
132
|
+
console.log(chalk.red(` ${testResult.error}`))
|
|
133
|
+
}
|
|
134
|
+
// Show errors from individual single runs
|
|
135
|
+
for (const run of testResult.singleRuns) {
|
|
136
|
+
if (!run.passed && run.error) {
|
|
137
|
+
console.log(chalk.red(` [run ${run.runIndex}] ${run.error}`))
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Show failed expectations
|
|
141
|
+
for (const exp of testResult.expectationResults) {
|
|
142
|
+
if (!exp.passed) {
|
|
143
|
+
console.log(chalk.red(` [${exp.type}] ${exp.detail || 'failed'}`))
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log() // blank line between groups
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Step 4: Create batch
|
|
153
|
+
let batchId: number | null = null
|
|
154
|
+
if (runIds.length > 0) {
|
|
155
|
+
try {
|
|
156
|
+
const passedCount = allResults.filter((r) => r.passed).length
|
|
157
|
+
const allPassed = passedCount === allResults.length
|
|
158
|
+
|
|
159
|
+
const batch = await createBatch(serverUrl, apiKey, {
|
|
160
|
+
testGroupRunIds: runIds,
|
|
161
|
+
status: allPassed ? 'success' : 'failed',
|
|
162
|
+
passed: allPassed,
|
|
163
|
+
summary: `CI: ${passedCount}/${allResults.length} passed`,
|
|
164
|
+
gitBranch, gitCommit,
|
|
165
|
+
startedAt: new Date(overallStart).toISOString(),
|
|
166
|
+
completedAt: new Date().toISOString(),
|
|
167
|
+
})
|
|
168
|
+
batchId = batch.id
|
|
169
|
+
} catch {
|
|
170
|
+
// Non-fatal
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Step 5: Print summary
|
|
175
|
+
const passed = allResults.filter((r) => r.passed).length
|
|
176
|
+
const failed = allResults.filter((r) => !r.passed).length
|
|
177
|
+
const durationMs = Date.now() - overallStart
|
|
178
|
+
|
|
179
|
+
console.log(chalk.white.bold('─'.repeat(50)))
|
|
180
|
+
console.log(chalk.white.bold('Summary'))
|
|
181
|
+
console.log(chalk.white.bold('─'.repeat(50)))
|
|
182
|
+
console.log(` Total: ${allResults.length}`)
|
|
183
|
+
console.log(` ${chalk.green(`Passed: ${passed}`)}`)
|
|
184
|
+
if (failed > 0) {
|
|
185
|
+
console.log(` ${chalk.red(`Failed: ${failed}`)}`)
|
|
186
|
+
}
|
|
187
|
+
console.log(` Duration: ${(durationMs / 1000).toFixed(1)}s`)
|
|
188
|
+
if (batchId) {
|
|
189
|
+
console.log(` Batch ID: ${batchId}`)
|
|
190
|
+
}
|
|
191
|
+
console.log(chalk.white.bold('─'.repeat(50)))
|
|
192
|
+
|
|
193
|
+
if (failed > 0) {
|
|
194
|
+
console.log(chalk.red(`\n[elasticdash ci] ${failed} test(s) failed.\n`))
|
|
195
|
+
} else {
|
|
196
|
+
console.log(chalk.green(`\n[elasticdash ci] All tests passed.\n`))
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
total: allResults.length,
|
|
201
|
+
passed,
|
|
202
|
+
failed,
|
|
203
|
+
skipped: 0,
|
|
204
|
+
durationMs,
|
|
205
|
+
batchId,
|
|
206
|
+
results: allResults,
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import fg from 'fast-glob'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
|
|
4
|
+
export interface DiscoverOptions {
|
|
5
|
+
cwd?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function discoverTestFiles(options?: DiscoverOptions): Promise<string[]> {
|
|
9
|
+
const cwd = options?.cwd ?? process.cwd()
|
|
10
|
+
const files = await fg('**/ed_tests.{ts,js}', {
|
|
11
|
+
cwd,
|
|
12
|
+
absolute: true,
|
|
13
|
+
ignore: ['**/node_modules/**', '**/.git/**', '**/.ed_traces/**'],
|
|
14
|
+
})
|
|
15
|
+
return files.sort()
|
|
16
|
+
}
|