@mergifyio/ci-core 0.1.1

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 ADDED
@@ -0,0 +1,17 @@
1
+ # @mergifyio/ci-core
2
+
3
+ Internal shared core for Mergify's test-framework reporters.
4
+
5
+ This package is not intended for direct consumption. It is consumed by the
6
+ published framework reporters:
7
+
8
+ - [`@mergifyio/vitest`](../vitest) — Vitest reporter.
9
+ - [`@mergifyio/playwright`](../playwright) — Playwright reporter.
10
+
11
+ It provides reporter-agnostic helpers for OpenTelemetry span emission, CI
12
+ provider / repository / Git resource detection, quarantine and flaky-detection
13
+ API clients, and the shared `TestCaseResult` / `TestRunSession` types.
14
+
15
+ API stability is **not** guaranteed across minor versions — breaking changes
16
+ land without deprecation cycles. Pin the consuming package (`@mergifyio/vitest`
17
+ or `@mergifyio/playwright`) instead.
package/dist/index.cjs ADDED
@@ -0,0 +1,560 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let node_child_process = require("node:child_process");
3
+ let node_crypto = require("node:crypto");
4
+ let _opentelemetry_resources = require("@opentelemetry/resources");
5
+ let node_fs = require("node:fs");
6
+ let _opentelemetry_api = require("@opentelemetry/api");
7
+ let _opentelemetry_core = require("@opentelemetry/core");
8
+ let _opentelemetry_exporter_trace_otlp_proto = require("@opentelemetry/exporter-trace-otlp-proto");
9
+ let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
10
+ //#region src/utils.ts
11
+ /**
12
+ * Generate a 16-character hex test run ID (8 random bytes).
13
+ */
14
+ function generateTestRunId() {
15
+ return (0, node_crypto.randomBytes)(8).toString("hex");
16
+ }
17
+ const TRUTHY_VALUES = new Set([
18
+ "y",
19
+ "yes",
20
+ "t",
21
+ "true",
22
+ "on",
23
+ "1"
24
+ ]);
25
+ const FALSY_VALUES = new Set([
26
+ "n",
27
+ "no",
28
+ "f",
29
+ "false",
30
+ "off",
31
+ "0"
32
+ ]);
33
+ /** Convert a string to a boolean. */
34
+ function strtobool(value) {
35
+ const lower = value.toLowerCase();
36
+ if (TRUTHY_VALUES.has(lower)) return true;
37
+ if (FALSY_VALUES.has(lower)) return false;
38
+ throw new Error(`Could not convert '${value}' to boolean`);
39
+ }
40
+ function envToBool(value, fallback) {
41
+ if (value === void 0) return false;
42
+ try {
43
+ return strtobool(value);
44
+ } catch {
45
+ return fallback;
46
+ }
47
+ }
48
+ /** Check if running in a CI environment. */
49
+ function isInCI() {
50
+ return envToBool(process.env.CI, !!(process.env.CI ?? "").length);
51
+ }
52
+ /** Detect the current CI provider from environment variables. */
53
+ function getCIProvider() {
54
+ if (process.env.GITHUB_ACTIONS) return "github_actions";
55
+ if (process.env.CIRCLECI) return "circleci";
56
+ if (process.env.JENKINS_URL) return "jenkins";
57
+ if (process.env.BUILDKITE) return "buildkite";
58
+ return null;
59
+ }
60
+ /** Execute a git command and return trimmed stdout, or null on failure. */
61
+ function git(...args) {
62
+ try {
63
+ return (0, node_child_process.execSync)(`git ${args.join(" ")}`, {
64
+ encoding: "utf-8",
65
+ stdio: [
66
+ "pipe",
67
+ "pipe",
68
+ "pipe"
69
+ ]
70
+ }).trim();
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ /** Split an "owner/repo" string into parts. */
76
+ function splitRepoName(fullName) {
77
+ const parts = fullName.split("/");
78
+ if (parts.length !== 2 || !parts[0] || !parts[1]) throw new Error(`Invalid repository name: ${fullName}`);
79
+ return {
80
+ owner: parts[0],
81
+ repo: parts[1]
82
+ };
83
+ }
84
+ /**
85
+ * Resolve the repository name ("owner/repo") from env vars or the git remote.
86
+ * Checks GITHUB_REPOSITORY, then GIT_URL, then falls back to `git config`.
87
+ */
88
+ function getRepoName() {
89
+ if (process.env.GITHUB_REPOSITORY) return process.env.GITHUB_REPOSITORY;
90
+ if (process.env.GIT_URL) return getRepositoryNameFromUrl(process.env.GIT_URL) ?? void 0;
91
+ const remoteUrl = git("config", "--get", "remote.origin.url");
92
+ if (remoteUrl) return getRepositoryNameFromUrl(remoteUrl) ?? void 0;
93
+ }
94
+ /** Parse a repository name from a git remote URL (SSH or HTTPS). */
95
+ function getRepositoryNameFromUrl(url) {
96
+ const sshMatch = url.match(/:([^/]+\/[^/]+?)(?:\.git)?$/);
97
+ if (sshMatch) return sshMatch[1];
98
+ try {
99
+ const path = new URL(url).pathname.replace(/^\//, "").replace(/\.git$/, "");
100
+ if (path.includes("/")) return path;
101
+ } catch {}
102
+ return null;
103
+ }
104
+ /**
105
+ * Resolve the branch name for quarantine/flaky-detection lookups from OTel
106
+ * resource attributes: `vcs.ref.base.name` (PR target) preferred, then
107
+ * `vcs.ref.head.name` (push branch / PR head). Empty strings fall through.
108
+ */
109
+ function resolveBranchFromAttributes(attrs) {
110
+ const base = attrs["vcs.ref.base.name"];
111
+ if (typeof base === "string" && base.length > 0) return base;
112
+ const head = attrs["vcs.ref.head.name"];
113
+ if (typeof head === "string" && head.length > 0) return head;
114
+ }
115
+ //#endregion
116
+ //#region src/flaky-detection.ts
117
+ async function fetchFlakyDetectionContext(config, logger) {
118
+ const { owner, repo } = splitRepoName(config.repoName);
119
+ const url = `${config.apiUrl}/v1/ci/${owner}/repositories/${repo}/flaky-detection-context`;
120
+ try {
121
+ const response = await fetch(url, {
122
+ headers: { Authorization: `Bearer ${config.token}` },
123
+ signal: AbortSignal.timeout(1e4)
124
+ });
125
+ if (response.status === 402) {
126
+ logger("Flaky detection not available (no subscription)");
127
+ return null;
128
+ }
129
+ if (!response.ok) {
130
+ logger(`Failed to fetch flaky detection context: HTTP ${response.status}`);
131
+ return null;
132
+ }
133
+ return await response.json();
134
+ } catch (err) {
135
+ if (err instanceof DOMException && err.name === "TimeoutError") logger("Flaky detection API request timed out");
136
+ else logger(`Failed to fetch flaky detection context: ${err}`);
137
+ return null;
138
+ }
139
+ }
140
+ var FlakyDetector = class {
141
+ context;
142
+ mode;
143
+ candidates;
144
+ existingTestsInSession;
145
+ budgetMs;
146
+ perTestDeadlineMs;
147
+ testMetrics = /* @__PURE__ */ new Map();
148
+ tooSlowTests = [];
149
+ constructor(context, mode, allTestNames) {
150
+ this.context = context;
151
+ this.mode = mode;
152
+ const existingSet = new Set(context.existing_test_names);
153
+ const unhealthySet = new Set(context.unhealthy_test_names);
154
+ this.existingTestsInSession = new Set(allTestNames.filter((t) => existingSet.has(t)));
155
+ if (mode === "new") this.candidates = new Set(allTestNames.filter((t) => !existingSet.has(t) && t.length <= context.max_test_name_length));
156
+ else this.candidates = new Set(allTestNames.filter((t) => unhealthySet.has(t) && t.length <= context.max_test_name_length));
157
+ const budgetRatio = mode === "new" ? context.budget_ratio_for_new_tests : context.budget_ratio_for_unhealthy_tests;
158
+ const totalDurationMs = context.existing_tests_mean_duration_ms * this.existingTestsInSession.size;
159
+ this.budgetMs = Math.max(budgetRatio * totalDurationMs, context.min_budget_duration_ms);
160
+ this.perTestDeadlineMs = this.candidates.size > 0 ? this.budgetMs / this.candidates.size : 0;
161
+ }
162
+ isCandidate(testName) {
163
+ return this.candidates.has(testName);
164
+ }
165
+ /** Calculate max repeats for a candidate test. Call after first execution to use actual duration. */
166
+ getMaxRepeats(testName, initialDurationMs) {
167
+ const metrics = this.getOrCreateMetrics(testName);
168
+ metrics.initialDurationMs = initialDurationMs;
169
+ if (initialDurationMs * this.context.min_test_execution_count > this.perTestDeadlineMs) {
170
+ metrics.tooSlow = true;
171
+ this.tooSlowTests.push(testName);
172
+ return 0;
173
+ }
174
+ const maxByBudget = initialDurationMs > 0 ? Math.floor(this.perTestDeadlineMs / initialDurationMs) - 1 : 0;
175
+ return Math.max(0, Math.min(maxByBudget, this.context.max_test_execution_count - 1));
176
+ }
177
+ recordOutcome(testName, outcome) {
178
+ const metrics = this.getOrCreateMetrics(testName);
179
+ metrics.outcomes.add(outcome);
180
+ metrics.rerunCount++;
181
+ }
182
+ isFlaky(testName) {
183
+ const metrics = this.testMetrics.get(testName);
184
+ if (!metrics) return false;
185
+ return metrics.outcomes.has("pass") && metrics.outcomes.has("fail");
186
+ }
187
+ getRerunCount(testName) {
188
+ return this.testMetrics.get(testName)?.rerunCount ?? 0;
189
+ }
190
+ isTooSlow(testName) {
191
+ return this.testMetrics.get(testName)?.tooSlow ?? false;
192
+ }
193
+ /** Get summary data for the terminal report. */
194
+ getSummary() {
195
+ const rerunTests = [];
196
+ for (const [name, metrics] of this.testMetrics) if (metrics.rerunCount > 0) rerunTests.push({
197
+ name,
198
+ rerunCount: metrics.rerunCount,
199
+ flaky: this.isFlaky(name),
200
+ outcomes: [...metrics.outcomes]
201
+ });
202
+ return {
203
+ mode: this.mode,
204
+ budgetMs: this.budgetMs,
205
+ candidateCount: this.candidates.size,
206
+ rerunTests,
207
+ tooSlowTests: this.tooSlowTests
208
+ };
209
+ }
210
+ getOrCreateMetrics(testName) {
211
+ let metrics = this.testMetrics.get(testName);
212
+ if (!metrics) {
213
+ metrics = {
214
+ outcomes: /* @__PURE__ */ new Set(),
215
+ rerunCount: 0,
216
+ initialDurationMs: 0,
217
+ tooSlow: false
218
+ };
219
+ this.testMetrics.set(testName, metrics);
220
+ }
221
+ return metrics;
222
+ }
223
+ };
224
+ //#endregion
225
+ //#region src/quarantine.ts
226
+ const LINK_VALUE_PATTERN = /^<([^>]+)>\s*;\s*(.+)$/;
227
+ const REL_PARAMETER_PATTERN = /rel\s*=\s*(?:"([^"]+)"|([^\s;,]+))/i;
228
+ function parseNextLink(linkHeader, baseUrl) {
229
+ if (linkHeader === null || linkHeader.length === 0) return null;
230
+ for (const part of linkHeader.split(",")) {
231
+ const match = part.trim().match(LINK_VALUE_PATTERN);
232
+ if (!match) continue;
233
+ const relMatch = match[2].match(REL_PARAMETER_PATTERN);
234
+ if (!relMatch) continue;
235
+ if ((relMatch[1] ?? relMatch[2]).toLowerCase().split(/\s+/).includes("next")) return new URL(match[1], baseUrl).toString();
236
+ }
237
+ return null;
238
+ }
239
+ function buildInitialUrl(config) {
240
+ const { owner, repo } = splitRepoName(config.repoName);
241
+ return `${`${config.apiUrl}/v1/ci/${owner}/repositories/${repo}/quarantines`}?branch=${encodeURIComponent(config.branch)}&per_page=100`;
242
+ }
243
+ async function fetchQuarantineList(config, logger) {
244
+ const collected = /* @__PURE__ */ new Set();
245
+ const seen = /* @__PURE__ */ new Set();
246
+ let url = buildInitialUrl(config);
247
+ try {
248
+ while (url !== null) {
249
+ if (seen.has(url)) {
250
+ logger("Quarantine API returned a cyclic `next` link, aborting");
251
+ return /* @__PURE__ */ new Set();
252
+ }
253
+ seen.add(url);
254
+ const response = await fetch(url, {
255
+ headers: { Authorization: `Bearer ${config.token}` },
256
+ signal: AbortSignal.timeout(1e4)
257
+ });
258
+ if (response.status === 402) {
259
+ logger("Quarantine not available (no subscription)");
260
+ return /* @__PURE__ */ new Set();
261
+ }
262
+ if (!response.ok) {
263
+ logger(`Failed to fetch quarantine list: HTTP ${response.status}`);
264
+ return /* @__PURE__ */ new Set();
265
+ }
266
+ const data = await response.json();
267
+ for (const t of data.quarantined_tests) collected.add(t.test_name);
268
+ url = parseNextLink(response.headers.get("link"), url);
269
+ }
270
+ } catch (err) {
271
+ if (err instanceof DOMException && err.name === "TimeoutError") logger("Quarantine API request timed out");
272
+ else logger(`Failed to fetch quarantine list: ${err}`);
273
+ return /* @__PURE__ */ new Set();
274
+ }
275
+ return collected;
276
+ }
277
+ //#endregion
278
+ //#region src/resources/buildkite.ts
279
+ function detect$5() {
280
+ if (!process.env.BUILDKITE) return {};
281
+ const attrs = {};
282
+ const env = process.env;
283
+ if (env.BUILDKITE_PIPELINE_SLUG) attrs["cicd.pipeline.name"] = env.BUILDKITE_PIPELINE_SLUG;
284
+ if (env.BUILDKITE_LABEL || env.BUILDKITE_STEP_KEY) attrs["cicd.pipeline.task.name"] = env.BUILDKITE_LABEL || env.BUILDKITE_STEP_KEY || "";
285
+ if (env.BUILDKITE_BUILD_ID) attrs["cicd.pipeline.run.id"] = env.BUILDKITE_BUILD_ID;
286
+ if (env.BUILDKITE_BUILD_URL) attrs["cicd.pipeline.run.url"] = env.BUILDKITE_BUILD_URL;
287
+ if (env.BUILDKITE_RETRY_COUNT !== void 0) {
288
+ const retryCount = Number(env.BUILDKITE_RETRY_COUNT);
289
+ if (!Number.isNaN(retryCount)) attrs["cicd.pipeline.run.attempt"] = retryCount + 1;
290
+ }
291
+ if (env.BUILDKITE_AGENT_NAME) attrs["cicd.pipeline.runner.name"] = env.BUILDKITE_AGENT_NAME;
292
+ if (env.BUILDKITE_BRANCH) attrs["vcs.ref.head.name"] = env.BUILDKITE_BRANCH;
293
+ if (env.BUILDKITE_PULL_REQUEST_BASE_BRANCH) attrs["vcs.ref.base.name"] = env.BUILDKITE_PULL_REQUEST_BASE_BRANCH;
294
+ if (env.BUILDKITE_COMMIT) attrs["vcs.ref.head.revision"] = env.BUILDKITE_COMMIT;
295
+ if (env.BUILDKITE_REPO) {
296
+ attrs["vcs.repository.url.full"] = env.BUILDKITE_REPO;
297
+ const repoName = getRepositoryNameFromUrl(env.BUILDKITE_REPO);
298
+ if (repoName) attrs["vcs.repository.name"] = repoName;
299
+ }
300
+ return attrs;
301
+ }
302
+ //#endregion
303
+ //#region src/resources/ci.ts
304
+ function detect$4() {
305
+ const provider = getCIProvider();
306
+ if (!provider) return {};
307
+ return { "cicd.provider.name": provider };
308
+ }
309
+ //#endregion
310
+ //#region src/resources/git.ts
311
+ /** Fallback VCS detection via git CLI. Only runs when a CI provider is detected. */
312
+ function detect$3() {
313
+ if (!getCIProvider()) return {};
314
+ const attrs = {};
315
+ const branch = git("rev-parse", "--abbrev-ref", "HEAD");
316
+ if (branch && branch !== "HEAD") attrs["vcs.ref.head.name"] = branch;
317
+ const revision = git("rev-parse", "HEAD");
318
+ if (revision) attrs["vcs.ref.head.revision"] = revision;
319
+ const remoteUrl = git("config", "--get", "remote.origin.url");
320
+ if (remoteUrl) {
321
+ attrs["vcs.repository.url.full"] = remoteUrl;
322
+ const repoName = getRepositoryNameFromUrl(remoteUrl);
323
+ if (repoName) attrs["vcs.repository.name"] = repoName;
324
+ }
325
+ return attrs;
326
+ }
327
+ //#endregion
328
+ //#region src/resources/github-actions.ts
329
+ function getHeadRefName() {
330
+ return process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME;
331
+ }
332
+ function getHeadRevision() {
333
+ const eventPath = process.env.GITHUB_EVENT_PATH;
334
+ if (eventPath) try {
335
+ const event = JSON.parse((0, node_fs.readFileSync)(eventPath, "utf-8"));
336
+ if (event?.pull_request?.head?.sha) return event.pull_request.head.sha;
337
+ } catch {}
338
+ return process.env.GITHUB_SHA;
339
+ }
340
+ function getRepositoryUrl() {
341
+ const serverUrl = process.env.GITHUB_SERVER_URL;
342
+ const repo = process.env.GITHUB_REPOSITORY;
343
+ if (serverUrl && repo) return `${serverUrl}/${repo}`;
344
+ }
345
+ function detect$2() {
346
+ if (!process.env.GITHUB_ACTIONS) return {};
347
+ const attrs = {};
348
+ const env = process.env;
349
+ if (env.GITHUB_WORKFLOW) attrs["cicd.pipeline.name"] = env.GITHUB_WORKFLOW;
350
+ if (env.GITHUB_JOB) attrs["cicd.pipeline.task.name"] = env.GITHUB_JOB;
351
+ if (env.GITHUB_RUN_ID) attrs["cicd.pipeline.run.id"] = Number(env.GITHUB_RUN_ID);
352
+ if (env.GITHUB_RUN_ATTEMPT) attrs["cicd.pipeline.run.attempt"] = Number(env.GITHUB_RUN_ATTEMPT);
353
+ if (env.RUNNER_NAME) attrs["cicd.pipeline.runner.name"] = env.RUNNER_NAME;
354
+ const headRef = getHeadRefName();
355
+ if (headRef) attrs["vcs.ref.head.name"] = headRef;
356
+ if (env.GITHUB_REF_TYPE) attrs["vcs.ref.head.type"] = env.GITHUB_REF_TYPE;
357
+ if (env.GITHUB_BASE_REF) attrs["vcs.ref.base.name"] = env.GITHUB_BASE_REF;
358
+ if (env.GITHUB_REPOSITORY) attrs["vcs.repository.name"] = env.GITHUB_REPOSITORY;
359
+ if (env.GITHUB_REPOSITORY_ID) attrs["vcs.repository.id"] = Number(env.GITHUB_REPOSITORY_ID);
360
+ const repoUrl = getRepositoryUrl();
361
+ if (repoUrl) attrs["vcs.repository.url.full"] = repoUrl;
362
+ const revision = getHeadRevision();
363
+ if (revision) attrs["vcs.ref.head.revision"] = revision;
364
+ return attrs;
365
+ }
366
+ //#endregion
367
+ //#region src/resources/jenkins.ts
368
+ function stripBranchPrefix(branch) {
369
+ return branch.replace(/^origin\//, "").replace(/^refs\/heads\//, "");
370
+ }
371
+ function detect$1() {
372
+ if (!process.env.JENKINS_URL) return {};
373
+ const attrs = {};
374
+ const env = process.env;
375
+ if (env.JOB_NAME) {
376
+ attrs["cicd.pipeline.name"] = env.JOB_NAME;
377
+ attrs["cicd.pipeline.task.name"] = env.JOB_NAME;
378
+ }
379
+ if (env.BUILD_ID) attrs["cicd.pipeline.run.id"] = env.BUILD_ID;
380
+ if (env.BUILD_URL) attrs["cicd.pipeline.run.url"] = env.BUILD_URL;
381
+ if (env.NODE_NAME) attrs["cicd.pipeline.runner.name"] = env.NODE_NAME;
382
+ if (env.GIT_BRANCH) attrs["vcs.ref.head.name"] = stripBranchPrefix(env.GIT_BRANCH);
383
+ if (env.GIT_COMMIT) attrs["vcs.ref.head.revision"] = env.GIT_COMMIT;
384
+ if (env.GIT_URL) {
385
+ attrs["vcs.repository.url.full"] = env.GIT_URL;
386
+ const repoName = getRepositoryNameFromUrl(env.GIT_URL);
387
+ if (repoName) attrs["vcs.repository.name"] = repoName;
388
+ }
389
+ return attrs;
390
+ }
391
+ //#endregion
392
+ //#region src/resources/mergify.ts
393
+ function detect() {
394
+ const jobName = process.env.MERGIFY_TEST_JOB_NAME;
395
+ if (jobName) return { "mergify.test.job.name": jobName };
396
+ return {};
397
+ }
398
+ //#endregion
399
+ //#region src/resources/index.ts
400
+ function detectResources(frameworkAttributes, testRunId) {
401
+ return (0, _opentelemetry_resources.resourceFromAttributes)({
402
+ ...detect$3(),
403
+ ...detect$4(),
404
+ ...detect$2(),
405
+ ...detect$1(),
406
+ ...detect$5(),
407
+ ...detect(),
408
+ ...frameworkAttributes,
409
+ "test.run.id": testRunId
410
+ });
411
+ }
412
+ //#endregion
413
+ //#region src/spans.ts
414
+ function startSessionSpan(tracing, name) {
415
+ let parentContext = _opentelemetry_api.context.active();
416
+ const traceparent = process.env.MERGIFY_TRACEPARENT;
417
+ if (traceparent) {
418
+ const carrier = { traceparent };
419
+ parentContext = new _opentelemetry_core.W3CTraceContextPropagator().extract(_opentelemetry_api.context.active(), carrier, {
420
+ get(c, key) {
421
+ return c[key];
422
+ },
423
+ keys(c) {
424
+ return Object.keys(c);
425
+ }
426
+ });
427
+ }
428
+ return tracing.tracer.startSpan(name, { attributes: { "test.scope": "session" } }, parentContext);
429
+ }
430
+ async function endSessionSpan(tracing, sessionSpan, reason) {
431
+ sessionSpan.setStatus({ code: reason === "failed" ? _opentelemetry_api.SpanStatusCode.ERROR : _opentelemetry_api.SpanStatusCode.OK });
432
+ sessionSpan.end();
433
+ let flushError;
434
+ try {
435
+ await tracing.tracerProvider.forceFlush();
436
+ } catch (err) {
437
+ flushError = err;
438
+ }
439
+ if (tracing.ownsExporter) try {
440
+ await tracing.tracerProvider.shutdown();
441
+ } catch {}
442
+ if (flushError !== void 0) throw flushError;
443
+ }
444
+ function emitTestCaseSpan(tracer, sessionSpan, result) {
445
+ const parentCtx = _opentelemetry_api.trace.setSpan(_opentelemetry_api.context.active(), sessionSpan);
446
+ const startTimeMs = result.startTime;
447
+ const endTimeMs = startTimeMs + result.duration;
448
+ const attributes = {
449
+ "code.filepath": result.filepath,
450
+ "code.function": result.function,
451
+ "code.lineno": result.lineno,
452
+ "code.namespace": result.namespace,
453
+ "code.file.path": result.absoluteFilepath,
454
+ "code.line.number": result.lineno,
455
+ "test.scope": "case",
456
+ "test.case.result.status": result.status,
457
+ "cicd.test.retry_count": result.retryCount
458
+ };
459
+ if (result.project !== void 0) attributes["cicd.test.project"] = result.project;
460
+ if (result.quarantined !== void 0) attributes["cicd.test.quarantined"] = result.quarantined;
461
+ if (result.flakyDetection) {
462
+ attributes["cicd.test.flaky_detection"] = true;
463
+ attributes["cicd.test.new"] = result.flakyDetection.new;
464
+ attributes["cicd.test.flaky"] = result.flakyDetection.flaky;
465
+ attributes["cicd.test.rerun_count"] = result.flakyDetection.rerunCount;
466
+ }
467
+ const spanName = result.namespace.length > 0 ? `${result.namespace} > ${result.function}` : result.function;
468
+ const span = tracer.startSpan(spanName, {
469
+ attributes,
470
+ startTime: startTimeMs
471
+ }, parentCtx);
472
+ if (result.error) span.setAttributes({
473
+ "exception.type": result.error.type,
474
+ "exception.message": result.error.message,
475
+ "exception.stacktrace": result.error.stacktrace
476
+ });
477
+ span.setStatus({ code: result.status === "failed" ? _opentelemetry_api.SpanStatusCode.ERROR : _opentelemetry_api.SpanStatusCode.OK });
478
+ span.end(endTimeMs);
479
+ }
480
+ //#endregion
481
+ //#region src/tracing.ts
482
+ var SynchronousBatchSpanProcessor = class {
483
+ exporter;
484
+ queue = [];
485
+ constructor(exporter) {
486
+ this.exporter = exporter;
487
+ }
488
+ onStart() {}
489
+ onEnd(span) {
490
+ if (span.spanContext().traceFlags & 1) this.queue.push(span);
491
+ }
492
+ forceFlush() {
493
+ return new Promise((resolve, reject) => {
494
+ this.exporter.export(this.queue, (result) => {
495
+ this.queue = [];
496
+ if (result.error) reject(result.error);
497
+ else resolve();
498
+ });
499
+ });
500
+ }
501
+ shutdown() {
502
+ return this.forceFlush().then(() => this.exporter.shutdown());
503
+ }
504
+ };
505
+ function createExporter(config) {
506
+ if (envToBool(process.env.MERGIFY_CI_DEBUG, false)) return new _opentelemetry_sdk_trace_base.ConsoleSpanExporter();
507
+ if (!config.token || !config.repoName) return null;
508
+ const { owner, repo } = splitRepoName(config.repoName);
509
+ return new _opentelemetry_exporter_trace_otlp_proto.OTLPTraceExporter({
510
+ url: `${config.apiUrl}/v1/ci/${owner}/repositories/${repo}/traces`,
511
+ headers: { Authorization: `Bearer ${config.token}` },
512
+ compression: "gzip"
513
+ });
514
+ }
515
+ function createTracing(config) {
516
+ let exporter;
517
+ let ownsExporter;
518
+ if (config.exporter) {
519
+ exporter = config.exporter;
520
+ ownsExporter = false;
521
+ } else {
522
+ exporter = createExporter(config);
523
+ ownsExporter = true;
524
+ }
525
+ if (!exporter) return null;
526
+ const resource = detectResources(config.frameworkAttributes, config.testRunId);
527
+ const tracerProvider = new _opentelemetry_sdk_trace_base.BasicTracerProvider({
528
+ resource,
529
+ spanProcessors: [config.exporter || envToBool(process.env.MERGIFY_CI_DEBUG, false) ? new _opentelemetry_sdk_trace_base.SimpleSpanProcessor(exporter) : new SynchronousBatchSpanProcessor(exporter)]
530
+ });
531
+ return {
532
+ tracer: tracerProvider.getTracer(config.tracerName),
533
+ tracerProvider,
534
+ exporter,
535
+ resource,
536
+ ownsExporter
537
+ };
538
+ }
539
+ //#endregion
540
+ exports.FlakyDetector = FlakyDetector;
541
+ exports.SynchronousBatchSpanProcessor = SynchronousBatchSpanProcessor;
542
+ exports.createTracing = createTracing;
543
+ exports.detectResources = detectResources;
544
+ exports.emitTestCaseSpan = emitTestCaseSpan;
545
+ exports.endSessionSpan = endSessionSpan;
546
+ exports.envToBool = envToBool;
547
+ exports.fetchFlakyDetectionContext = fetchFlakyDetectionContext;
548
+ exports.fetchQuarantineList = fetchQuarantineList;
549
+ exports.generateTestRunId = generateTestRunId;
550
+ exports.getCIProvider = getCIProvider;
551
+ exports.getRepoName = getRepoName;
552
+ exports.getRepositoryNameFromUrl = getRepositoryNameFromUrl;
553
+ exports.git = git;
554
+ exports.isInCI = isInCI;
555
+ exports.resolveBranchFromAttributes = resolveBranchFromAttributes;
556
+ exports.splitRepoName = splitRepoName;
557
+ exports.startSessionSpan = startSessionSpan;
558
+ exports.strtobool = strtobool;
559
+
560
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["detect","detect","detect","detect","detect","git.detect","ci.detect","githubActions.detect","jenkins.detect","buildkite.detect","mergify.detect","context","W3CTraceContextPropagator","SpanStatusCode","trace","ConsoleSpanExporter","OTLPTraceExporter","BasicTracerProvider","SimpleSpanProcessor"],"sources":["../src/utils.ts","../src/flaky-detection.ts","../src/quarantine.ts","../src/resources/buildkite.ts","../src/resources/ci.ts","../src/resources/git.ts","../src/resources/github-actions.ts","../src/resources/jenkins.ts","../src/resources/mergify.ts","../src/resources/index.ts","../src/spans.ts","../src/tracing.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { randomBytes } from 'node:crypto';\nimport type { Attributes } from '@opentelemetry/api';\n\nexport type CIProvider = 'github_actions' | 'jenkins' | 'circleci' | 'buildkite';\n\n/**\n * Generate a 16-character hex test run ID (8 random bytes).\n */\nexport function generateTestRunId(): string {\n return randomBytes(8).toString('hex');\n}\n\nconst TRUTHY_VALUES = new Set(['y', 'yes', 't', 'true', 'on', '1']);\nconst FALSY_VALUES = new Set(['n', 'no', 'f', 'false', 'off', '0']);\n\n/** Convert a string to a boolean. */\nexport function strtobool(value: string): boolean {\n const lower = value.toLowerCase();\n if (TRUTHY_VALUES.has(lower)) return true;\n if (FALSY_VALUES.has(lower)) return false;\n throw new Error(`Could not convert '${value}' to boolean`);\n}\n\nexport function envToBool(value: string | undefined, fallback: boolean): boolean {\n if (value === undefined) return false;\n try {\n return strtobool(value);\n } catch {\n return fallback;\n }\n}\n\n/** Check if running in a CI environment. */\nexport function isInCI(): boolean {\n return envToBool(process.env.CI, !!(process.env.CI ?? '').length);\n}\n\n/** Detect the current CI provider from environment variables. */\nexport function getCIProvider(): CIProvider | null {\n if (process.env.GITHUB_ACTIONS) return 'github_actions';\n if (process.env.CIRCLECI) return 'circleci';\n if (process.env.JENKINS_URL) return 'jenkins';\n if (process.env.BUILDKITE) return 'buildkite';\n return null;\n}\n\n/** Execute a git command and return trimmed stdout, or null on failure. */\nexport function git(...args: string[]): string | null {\n try {\n return execSync(`git ${args.join(' ')}`, {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch {\n return null;\n }\n}\n\n/** Split an \"owner/repo\" string into parts. */\nexport function splitRepoName(fullName: string): { owner: string; repo: string } {\n const parts = fullName.split('/');\n if (parts.length !== 2 || !parts[0] || !parts[1]) {\n throw new Error(`Invalid repository name: ${fullName}`);\n }\n return { owner: parts[0], repo: parts[1] };\n}\n\n/**\n * Resolve the repository name (\"owner/repo\") from env vars or the git remote.\n * Checks GITHUB_REPOSITORY, then GIT_URL, then falls back to `git config`.\n */\nexport function getRepoName(): string | undefined {\n if (process.env.GITHUB_REPOSITORY) return process.env.GITHUB_REPOSITORY;\n if (process.env.GIT_URL) {\n return getRepositoryNameFromUrl(process.env.GIT_URL) ?? undefined;\n }\n const remoteUrl = git('config', '--get', 'remote.origin.url');\n if (remoteUrl) return getRepositoryNameFromUrl(remoteUrl) ?? undefined;\n return undefined;\n}\n\n/** Parse a repository name from a git remote URL (SSH or HTTPS). */\nexport function getRepositoryNameFromUrl(url: string): string | null {\n // SSH: git@github.com:owner/repo.git\n const sshMatch = url.match(/:([^/]+\\/[^/]+?)(?:\\.git)?$/);\n if (sshMatch) return sshMatch[1];\n\n // HTTPS: https://github.com/owner/repo.git\n try {\n const parsed = new URL(url);\n const path = parsed.pathname.replace(/^\\//, '').replace(/\\.git$/, '');\n if (path.includes('/')) return path;\n } catch {\n // not a valid URL\n }\n\n return null;\n}\n\n/**\n * Resolve the branch name for quarantine/flaky-detection lookups from OTel\n * resource attributes: `vcs.ref.base.name` (PR target) preferred, then\n * `vcs.ref.head.name` (push branch / PR head). Empty strings fall through.\n */\nexport function resolveBranchFromAttributes(attrs: Attributes): string | undefined {\n const base = attrs['vcs.ref.base.name'];\n if (typeof base === 'string' && base.length > 0) return base;\n const head = attrs['vcs.ref.head.name'];\n if (typeof head === 'string' && head.length > 0) return head;\n return undefined;\n}\n","import { splitRepoName } from './utils.js';\n\nexport type FlakyDetectionContext = {\n budget_ratio_for_new_tests: number;\n budget_ratio_for_unhealthy_tests: number;\n existing_test_names: string[];\n existing_tests_mean_duration_ms: number;\n unhealthy_test_names: string[];\n max_test_execution_count: number;\n max_test_name_length: number;\n min_budget_duration_ms: number;\n min_test_execution_count: number;\n};\n\nexport type FlakyDetectionMode = 'new' | 'unhealthy';\n\nexport type FlakyDetectionConfig = {\n apiUrl: string;\n token: string;\n repoName: string;\n};\n\nexport async function fetchFlakyDetectionContext(\n config: FlakyDetectionConfig,\n logger: (msg: string) => void\n): Promise<FlakyDetectionContext | null> {\n const { owner, repo } = splitRepoName(config.repoName);\n const url = `${config.apiUrl}/v1/ci/${owner}/repositories/${repo}/flaky-detection-context`;\n\n try {\n const response = await fetch(url, {\n headers: { Authorization: `Bearer ${config.token}` },\n signal: AbortSignal.timeout(10_000),\n });\n\n if (response.status === 402) {\n logger('Flaky detection not available (no subscription)');\n return null;\n }\n\n if (!response.ok) {\n logger(`Failed to fetch flaky detection context: HTTP ${response.status}`);\n return null;\n }\n\n return (await response.json()) as FlakyDetectionContext;\n } catch (err) {\n if (err instanceof DOMException && err.name === 'TimeoutError') {\n logger('Flaky detection API request timed out');\n } else {\n logger(`Failed to fetch flaky detection context: ${err}`);\n }\n return null;\n }\n}\n\ntype TestMetrics = {\n outcomes: Set<string>;\n rerunCount: number;\n initialDurationMs: number;\n tooSlow: boolean;\n};\n\nexport class FlakyDetector {\n private context: FlakyDetectionContext;\n private mode: FlakyDetectionMode;\n private candidates: Set<string>;\n private existingTestsInSession: Set<string>;\n private budgetMs: number;\n private perTestDeadlineMs: number;\n private testMetrics: Map<string, TestMetrics> = new Map();\n private tooSlowTests: string[] = [];\n\n constructor(context: FlakyDetectionContext, mode: FlakyDetectionMode, allTestNames: string[]) {\n this.context = context;\n this.mode = mode;\n\n const existingSet = new Set(context.existing_test_names);\n const unhealthySet = new Set(context.unhealthy_test_names);\n\n // Count existing tests in this session for budget calculation\n this.existingTestsInSession = new Set(allTestNames.filter((t) => existingSet.has(t)));\n\n // Identify candidates based on mode\n if (mode === 'new') {\n this.candidates = new Set(\n allTestNames.filter((t) => !existingSet.has(t) && t.length <= context.max_test_name_length)\n );\n } else {\n this.candidates = new Set(\n allTestNames.filter((t) => unhealthySet.has(t) && t.length <= context.max_test_name_length)\n );\n }\n\n // Calculate budget\n const budgetRatio =\n mode === 'new'\n ? context.budget_ratio_for_new_tests\n : context.budget_ratio_for_unhealthy_tests;\n const totalDurationMs =\n context.existing_tests_mean_duration_ms * this.existingTestsInSession.size;\n this.budgetMs = Math.max(budgetRatio * totalDurationMs, context.min_budget_duration_ms);\n\n // Static per-test deadline (xdist-style)\n this.perTestDeadlineMs = this.candidates.size > 0 ? this.budgetMs / this.candidates.size : 0;\n }\n\n isCandidate(testName: string): boolean {\n return this.candidates.has(testName);\n }\n\n /** Calculate max repeats for a candidate test. Call after first execution to use actual duration. */\n getMaxRepeats(testName: string, initialDurationMs: number): number {\n const metrics = this.getOrCreateMetrics(testName);\n metrics.initialDurationMs = initialDurationMs;\n\n // Check if test is too slow for even min_test_execution_count\n if (initialDurationMs * this.context.min_test_execution_count > this.perTestDeadlineMs) {\n metrics.tooSlow = true;\n this.tooSlowTests.push(testName);\n return 0;\n }\n\n // How many reruns fit in the per-test deadline?\n const maxByBudget =\n initialDurationMs > 0 ? Math.floor(this.perTestDeadlineMs / initialDurationMs) - 1 : 0;\n // Cap by max_test_execution_count (subtract 1 for the initial run)\n return Math.max(0, Math.min(maxByBudget, this.context.max_test_execution_count - 1));\n }\n\n recordOutcome(testName: string, outcome: 'pass' | 'fail'): void {\n const metrics = this.getOrCreateMetrics(testName);\n metrics.outcomes.add(outcome);\n metrics.rerunCount++;\n }\n\n isFlaky(testName: string): boolean {\n const metrics = this.testMetrics.get(testName);\n if (!metrics) return false;\n return metrics.outcomes.has('pass') && metrics.outcomes.has('fail');\n }\n\n getRerunCount(testName: string): number {\n return this.testMetrics.get(testName)?.rerunCount ?? 0;\n }\n\n isTooSlow(testName: string): boolean {\n return this.testMetrics.get(testName)?.tooSlow ?? false;\n }\n\n /** Get summary data for the terminal report. */\n getSummary(): {\n mode: FlakyDetectionMode;\n budgetMs: number;\n candidateCount: number;\n rerunTests: Array<{ name: string; rerunCount: number; flaky: boolean; outcomes: string[] }>;\n tooSlowTests: string[];\n } {\n const rerunTests: Array<{\n name: string;\n rerunCount: number;\n flaky: boolean;\n outcomes: string[];\n }> = [];\n\n for (const [name, metrics] of this.testMetrics) {\n if (metrics.rerunCount > 0) {\n rerunTests.push({\n name,\n rerunCount: metrics.rerunCount,\n flaky: this.isFlaky(name),\n outcomes: [...metrics.outcomes],\n });\n }\n }\n\n return {\n mode: this.mode,\n budgetMs: this.budgetMs,\n candidateCount: this.candidates.size,\n rerunTests,\n tooSlowTests: this.tooSlowTests,\n };\n }\n\n private getOrCreateMetrics(testName: string): TestMetrics {\n let metrics = this.testMetrics.get(testName);\n if (!metrics) {\n metrics = { outcomes: new Set(), rerunCount: 0, initialDurationMs: 0, tooSlow: false };\n this.testMetrics.set(testName, metrics);\n }\n return metrics;\n }\n}\n","import { splitRepoName } from './utils.js';\n\nexport interface QuarantineConfig {\n apiUrl: string;\n token: string;\n repoName: string;\n branch: string;\n}\n\ninterface QuarantineResponse {\n quarantined_tests: Array<{ test_name: string }>;\n}\n\n// Matches one RFC 8288 Link header value: `<url>; ...parameters`.\nconst LINK_VALUE_PATTERN = /^<([^>]+)>\\s*;\\s*(.+)$/;\n// Matches `rel=\"...\"` or `rel=token`, case-insensitive.\nconst REL_PARAMETER_PATTERN = /rel\\s*=\\s*(?:\"([^\"]+)\"|([^\\s;,]+))/i;\n\nfunction parseNextLink(linkHeader: string | null, baseUrl: string): string | null {\n if (linkHeader === null || linkHeader.length === 0) {\n return null;\n }\n for (const part of linkHeader.split(',')) {\n const match = part.trim().match(LINK_VALUE_PATTERN);\n if (!match) continue;\n const relMatch = match[2].match(REL_PARAMETER_PATTERN);\n if (!relMatch) continue;\n // RFC 8288 allows space-separated rel-types: `rel=\"next prev\"`. Rel-types\n // are case-insensitive, so normalize before comparing.\n const relValue = (relMatch[1] ?? relMatch[2]).toLowerCase();\n if (relValue.split(/\\s+/).includes('next')) {\n // Resolve against the request URL so relative `next` links work.\n return new URL(match[1], baseUrl).toString();\n }\n }\n return null;\n}\n\nfunction buildInitialUrl(config: QuarantineConfig): string {\n const { owner, repo } = splitRepoName(config.repoName);\n // Build the query string manually so spaces stay `%20` (encodeURIComponent),\n // matching the encoding the API expected before pagination was added.\n const base = `${config.apiUrl}/v1/ci/${owner}/repositories/${repo}/quarantines`;\n return `${base}?branch=${encodeURIComponent(config.branch)}&per_page=100`;\n}\n\nexport async function fetchQuarantineList(\n config: QuarantineConfig,\n logger: (msg: string) => void\n): Promise<Set<string>> {\n const collected = new Set<string>();\n const seen = new Set<string>();\n let url: string | null = buildInitialUrl(config);\n\n try {\n while (url !== null) {\n if (seen.has(url)) {\n logger('Quarantine API returned a cyclic `next` link, aborting');\n return new Set();\n }\n seen.add(url);\n\n // Pages must be walked sequentially: each request needs the prior page's\n // `next` link, so the awaits inside the loop are intentional.\n // eslint-disable-next-line no-await-in-loop\n const response = await fetch(url, {\n headers: { Authorization: `Bearer ${config.token}` },\n signal: AbortSignal.timeout(10_000),\n });\n\n if (response.status === 402) {\n logger('Quarantine not available (no subscription)');\n return new Set();\n }\n\n if (!response.ok) {\n logger(`Failed to fetch quarantine list: HTTP ${response.status}`);\n return new Set();\n }\n\n // eslint-disable-next-line no-await-in-loop\n const data = (await response.json()) as QuarantineResponse;\n for (const t of data.quarantined_tests) {\n collected.add(t.test_name);\n }\n\n url = parseNextLink(response.headers.get('link'), url);\n }\n } catch (err) {\n if (err instanceof DOMException && err.name === 'TimeoutError') {\n logger('Quarantine API request timed out');\n } else {\n logger(`Failed to fetch quarantine list: ${err}`);\n }\n return new Set();\n }\n\n return collected;\n}\n","import type { Attributes } from '@opentelemetry/api';\nimport { getRepositoryNameFromUrl } from '../utils.js';\n\nexport function detect(): Attributes {\n if (!process.env.BUILDKITE) return {};\n\n const attrs: Attributes = {};\n const env = process.env;\n\n if (env.BUILDKITE_PIPELINE_SLUG) attrs['cicd.pipeline.name'] = env.BUILDKITE_PIPELINE_SLUG;\n if (env.BUILDKITE_LABEL || env.BUILDKITE_STEP_KEY) {\n attrs['cicd.pipeline.task.name'] = env.BUILDKITE_LABEL || env.BUILDKITE_STEP_KEY || '';\n }\n if (env.BUILDKITE_BUILD_ID) attrs['cicd.pipeline.run.id'] = env.BUILDKITE_BUILD_ID;\n if (env.BUILDKITE_BUILD_URL) attrs['cicd.pipeline.run.url'] = env.BUILDKITE_BUILD_URL;\n if (env.BUILDKITE_RETRY_COUNT !== undefined) {\n const retryCount = Number(env.BUILDKITE_RETRY_COUNT);\n if (!Number.isNaN(retryCount)) attrs['cicd.pipeline.run.attempt'] = retryCount + 1;\n }\n if (env.BUILDKITE_AGENT_NAME) attrs['cicd.pipeline.runner.name'] = env.BUILDKITE_AGENT_NAME;\n\n if (env.BUILDKITE_BRANCH) attrs['vcs.ref.head.name'] = env.BUILDKITE_BRANCH;\n if (env.BUILDKITE_PULL_REQUEST_BASE_BRANCH)\n attrs['vcs.ref.base.name'] = env.BUILDKITE_PULL_REQUEST_BASE_BRANCH;\n if (env.BUILDKITE_COMMIT) attrs['vcs.ref.head.revision'] = env.BUILDKITE_COMMIT;\n if (env.BUILDKITE_REPO) {\n attrs['vcs.repository.url.full'] = env.BUILDKITE_REPO;\n const repoName = getRepositoryNameFromUrl(env.BUILDKITE_REPO);\n if (repoName) attrs['vcs.repository.name'] = repoName;\n }\n\n return attrs;\n}\n","import type { Attributes } from '@opentelemetry/api';\nimport { getCIProvider } from '../utils.js';\n\nexport function detect(): Attributes {\n const provider = getCIProvider();\n if (!provider) return {};\n return { 'cicd.provider.name': provider };\n}\n","import type { Attributes } from '@opentelemetry/api';\nimport { getCIProvider, getRepositoryNameFromUrl, git } from '../utils.js';\n\n/** Fallback VCS detection via git CLI. Only runs when a CI provider is detected. */\nexport function detect(): Attributes {\n if (!getCIProvider()) return {};\n\n const attrs: Attributes = {};\n\n const branch = git('rev-parse', '--abbrev-ref', 'HEAD');\n if (branch && branch !== 'HEAD') {\n attrs['vcs.ref.head.name'] = branch;\n }\n\n const revision = git('rev-parse', 'HEAD');\n if (revision) {\n attrs['vcs.ref.head.revision'] = revision;\n }\n\n const remoteUrl = git('config', '--get', 'remote.origin.url');\n if (remoteUrl) {\n attrs['vcs.repository.url.full'] = remoteUrl;\n const repoName = getRepositoryNameFromUrl(remoteUrl);\n if (repoName) {\n attrs['vcs.repository.name'] = repoName;\n }\n }\n\n return attrs;\n}\n","import { readFileSync } from 'node:fs';\nimport type { Attributes } from '@opentelemetry/api';\n\nfunction getHeadRefName(): string | undefined {\n return process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME;\n}\n\nfunction getHeadRevision(): string | undefined {\n const eventPath = process.env.GITHUB_EVENT_PATH;\n if (eventPath) {\n try {\n const event = JSON.parse(readFileSync(eventPath, 'utf-8'));\n if (event?.pull_request?.head?.sha) {\n return event.pull_request.head.sha;\n }\n } catch {\n // fall through to GITHUB_SHA\n }\n }\n return process.env.GITHUB_SHA;\n}\n\nfunction getRepositoryUrl(): string | undefined {\n const serverUrl = process.env.GITHUB_SERVER_URL;\n const repo = process.env.GITHUB_REPOSITORY;\n if (serverUrl && repo) return `${serverUrl}/${repo}`;\n return undefined;\n}\n\nexport function detect(): Attributes {\n if (!process.env.GITHUB_ACTIONS) return {};\n\n const attrs: Attributes = {};\n const env = process.env;\n\n if (env.GITHUB_WORKFLOW) attrs['cicd.pipeline.name'] = env.GITHUB_WORKFLOW;\n if (env.GITHUB_JOB) attrs['cicd.pipeline.task.name'] = env.GITHUB_JOB;\n if (env.GITHUB_RUN_ID) attrs['cicd.pipeline.run.id'] = Number(env.GITHUB_RUN_ID);\n if (env.GITHUB_RUN_ATTEMPT) attrs['cicd.pipeline.run.attempt'] = Number(env.GITHUB_RUN_ATTEMPT);\n if (env.RUNNER_NAME) attrs['cicd.pipeline.runner.name'] = env.RUNNER_NAME;\n\n const headRef = getHeadRefName();\n if (headRef) attrs['vcs.ref.head.name'] = headRef;\n if (env.GITHUB_REF_TYPE) attrs['vcs.ref.head.type'] = env.GITHUB_REF_TYPE;\n if (env.GITHUB_BASE_REF) attrs['vcs.ref.base.name'] = env.GITHUB_BASE_REF;\n\n if (env.GITHUB_REPOSITORY) attrs['vcs.repository.name'] = env.GITHUB_REPOSITORY;\n if (env.GITHUB_REPOSITORY_ID) attrs['vcs.repository.id'] = Number(env.GITHUB_REPOSITORY_ID);\n\n const repoUrl = getRepositoryUrl();\n if (repoUrl) attrs['vcs.repository.url.full'] = repoUrl;\n\n const revision = getHeadRevision();\n if (revision) attrs['vcs.ref.head.revision'] = revision;\n\n return attrs;\n}\n","import type { Attributes } from '@opentelemetry/api';\nimport { getRepositoryNameFromUrl } from '../utils.js';\n\nfunction stripBranchPrefix(branch: string): string {\n return branch.replace(/^origin\\//, '').replace(/^refs\\/heads\\//, '');\n}\n\nexport function detect(): Attributes {\n if (!process.env.JENKINS_URL) return {};\n\n const attrs: Attributes = {};\n const env = process.env;\n\n if (env.JOB_NAME) {\n attrs['cicd.pipeline.name'] = env.JOB_NAME;\n attrs['cicd.pipeline.task.name'] = env.JOB_NAME;\n }\n if (env.BUILD_ID) attrs['cicd.pipeline.run.id'] = env.BUILD_ID;\n if (env.BUILD_URL) attrs['cicd.pipeline.run.url'] = env.BUILD_URL;\n if (env.NODE_NAME) attrs['cicd.pipeline.runner.name'] = env.NODE_NAME;\n\n if (env.GIT_BRANCH) attrs['vcs.ref.head.name'] = stripBranchPrefix(env.GIT_BRANCH);\n if (env.GIT_COMMIT) attrs['vcs.ref.head.revision'] = env.GIT_COMMIT;\n if (env.GIT_URL) {\n attrs['vcs.repository.url.full'] = env.GIT_URL;\n const repoName = getRepositoryNameFromUrl(env.GIT_URL);\n if (repoName) attrs['vcs.repository.name'] = repoName;\n }\n\n return attrs;\n}\n","import type { Attributes } from '@opentelemetry/api';\n\nexport function detect(): Attributes {\n const jobName = process.env.MERGIFY_TEST_JOB_NAME;\n if (jobName) return { 'mergify.test.job.name': jobName };\n return {};\n}\n","import type { Attributes } from '@opentelemetry/api';\nimport { type Resource, resourceFromAttributes } from '@opentelemetry/resources';\nimport * as buildkite from './buildkite.js';\nimport * as ci from './ci.js';\nimport * as git from './git.js';\nimport * as githubActions from './github-actions.js';\nimport * as jenkins from './jenkins.js';\nimport * as mergify from './mergify.js';\n\nexport function detectResources(frameworkAttributes: Attributes, testRunId: string): Resource {\n return resourceFromAttributes({\n ...git.detect(),\n ...ci.detect(),\n ...githubActions.detect(),\n ...jenkins.detect(),\n ...buildkite.detect(),\n ...mergify.detect(),\n ...frameworkAttributes,\n 'test.run.id': testRunId,\n });\n}\n","import { context, type Span, SpanStatusCode, type Tracer, trace } from '@opentelemetry/api';\nimport { W3CTraceContextPropagator } from '@opentelemetry/core';\nimport type { TracingContext } from './tracing.js';\nimport type { TestCaseResult } from './types.js';\n\nexport function startSessionSpan(tracing: TracingContext, name: string): Span {\n let parentContext = context.active();\n\n const traceparent = process.env.MERGIFY_TRACEPARENT;\n if (traceparent) {\n const carrier = { traceparent };\n const propagator = new W3CTraceContextPropagator();\n parentContext = propagator.extract(context.active(), carrier, {\n get(c: Record<string, string>, key: string) {\n return c[key];\n },\n keys(c: Record<string, string>) {\n return Object.keys(c);\n },\n });\n }\n\n return tracing.tracer.startSpan(name, { attributes: { 'test.scope': 'session' } }, parentContext);\n}\n\nexport async function endSessionSpan(\n tracing: TracingContext,\n sessionSpan: Span,\n reason: 'passed' | 'failed' | 'interrupted'\n): Promise<void> {\n sessionSpan.setStatus({\n code: reason === 'failed' ? SpanStatusCode.ERROR : SpanStatusCode.OK,\n });\n sessionSpan.end();\n\n let flushError: unknown;\n try {\n await tracing.tracerProvider.forceFlush();\n } catch (err) {\n flushError = err;\n }\n\n if (tracing.ownsExporter) {\n try {\n await tracing.tracerProvider.shutdown();\n } catch {\n // ignore shutdown errors\n }\n }\n\n if (flushError !== undefined) {\n throw flushError;\n }\n}\n\nexport function emitTestCaseSpan(tracer: Tracer, sessionSpan: Span, result: TestCaseResult): void {\n const parentCtx = trace.setSpan(context.active(), sessionSpan);\n const startTimeMs = result.startTime;\n const endTimeMs = startTimeMs + result.duration;\n\n const attributes: Record<string, string | number | boolean> = {\n 'code.filepath': result.filepath,\n 'code.function': result.function,\n 'code.lineno': result.lineno,\n 'code.namespace': result.namespace,\n 'code.file.path': result.absoluteFilepath,\n 'code.line.number': result.lineno,\n 'test.scope': 'case',\n 'test.case.result.status': result.status,\n 'cicd.test.retry_count': result.retryCount,\n };\n\n if (result.project !== undefined) {\n attributes['cicd.test.project'] = result.project;\n }\n\n if (result.quarantined !== undefined) {\n attributes['cicd.test.quarantined'] = result.quarantined;\n }\n\n if (result.flakyDetection) {\n attributes['cicd.test.flaky_detection'] = true;\n attributes['cicd.test.new'] = result.flakyDetection.new;\n attributes['cicd.test.flaky'] = result.flakyDetection.flaky;\n attributes['cicd.test.rerun_count'] = result.flakyDetection.rerunCount;\n }\n\n const spanName =\n result.namespace.length > 0 ? `${result.namespace} > ${result.function}` : result.function;\n\n const span = tracer.startSpan(spanName, { attributes, startTime: startTimeMs }, parentCtx);\n\n if (result.error) {\n span.setAttributes({\n 'exception.type': result.error.type,\n 'exception.message': result.error.message,\n 'exception.stacktrace': result.error.stacktrace,\n });\n }\n\n span.setStatus({\n code: result.status === 'failed' ? SpanStatusCode.ERROR : SpanStatusCode.OK,\n });\n\n span.end(endTimeMs);\n}\n","import type { Attributes, Tracer } from '@opentelemetry/api';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';\nimport type { Resource } from '@opentelemetry/resources';\nimport {\n BasicTracerProvider,\n ConsoleSpanExporter,\n type ReadableSpan,\n SimpleSpanProcessor,\n type SpanExporter,\n type SpanProcessor,\n} from '@opentelemetry/sdk-trace-base';\nimport { detectResources } from './resources/index.js';\nimport { envToBool, splitRepoName } from './utils.js';\n\nexport interface TracingConfig {\n token: string | undefined;\n repoName: string | undefined;\n apiUrl: string;\n testRunId: string;\n frameworkAttributes: Attributes;\n tracerName: string;\n /** Injected exporter — bypasses CI and token checks. */\n exporter?: SpanExporter;\n}\n\nexport interface TracingContext {\n tracer: Tracer;\n tracerProvider: BasicTracerProvider;\n exporter: SpanExporter;\n resource: Resource;\n /** Whether the provider should be shut down on test run end. */\n ownsExporter: boolean;\n}\n\nexport class SynchronousBatchSpanProcessor implements SpanProcessor {\n private queue: ReadableSpan[] = [];\n\n constructor(private exporter: SpanExporter) {}\n\n onStart(): void {}\n\n onEnd(span: ReadableSpan): void {\n if (span.spanContext().traceFlags & 1) {\n this.queue.push(span);\n }\n }\n\n forceFlush(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.exporter.export(this.queue, (result) => {\n this.queue = [];\n if (result.error) {\n reject(result.error);\n } else {\n resolve();\n }\n });\n });\n }\n\n shutdown(): Promise<void> {\n return this.forceFlush().then(() => this.exporter.shutdown());\n }\n}\n\nfunction createExporter(config: TracingConfig): SpanExporter | null {\n if (envToBool(process.env.MERGIFY_CI_DEBUG, false)) {\n return new ConsoleSpanExporter();\n }\n\n if (!config.token || !config.repoName) {\n return null;\n }\n\n const { owner, repo } = splitRepoName(config.repoName);\n\n return new OTLPTraceExporter({\n url: `${config.apiUrl}/v1/ci/${owner}/repositories/${repo}/traces`,\n headers: { Authorization: `Bearer ${config.token}` },\n compression: 'gzip' as never,\n });\n}\n\nexport function createTracing(config: TracingConfig): TracingContext | null {\n let exporter: SpanExporter | null;\n let ownsExporter: boolean;\n\n if (config.exporter) {\n // Injected exporter — skip CI and token checks\n exporter = config.exporter;\n ownsExporter = false;\n } else {\n exporter = createExporter(config);\n ownsExporter = true;\n }\n\n if (!exporter) return null;\n\n const resource = detectResources(config.frameworkAttributes, config.testRunId);\n\n // Use SimpleSpanProcessor for injected/debug exporters (exports on each span end)\n // Use SynchronousBatchSpanProcessor for production (batches and exports on flush)\n const useSimpleProcessor = config.exporter || envToBool(process.env.MERGIFY_CI_DEBUG, false);\n const processor: SpanProcessor = useSimpleProcessor\n ? new SimpleSpanProcessor(exporter)\n : new SynchronousBatchSpanProcessor(exporter);\n\n const tracerProvider = new BasicTracerProvider({\n resource,\n spanProcessors: [processor],\n });\n\n const tracer = tracerProvider.getTracer(config.tracerName);\n\n return { tracer, tracerProvider, exporter, resource, ownsExporter };\n}\n"],"mappings":"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"}