@qualflare/cucumberjs 0.1.0 → 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/README.md +49 -14
- package/dist/formatter/index.cjs +214 -302
- package/dist/formatter/index.cjs.map +1 -1
- package/dist/formatter/index.d.cts +14 -1
- package/dist/formatter/index.d.ts +14 -1
- package/dist/formatter/index.js +214 -302
- package/dist/formatter/index.js.map +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/formatter/index.ts","../../src/formatter/formatter.ts","../../src/config/ci-detect.ts","../../src/config/git-detect.ts","../../src/config/resolve-config.ts","../../src/http/client.ts","../../src/shared/constants.ts","../../src/shared/logger.ts","../../src/http/backoff.ts","../../src/http/errors.ts","../../src/http/idempotency.ts","../../src/config/version.ts","../../src/formatter/attachment-budget.ts","../../src/formatter/attempt-tracker.ts","../../src/shared/duration.ts","../../src/formatter/case-builder.ts","../../src/formatter/step-mapper.ts","../../src/formatter/collect-builder.ts","../../src/formatter/gherkin-index.ts","../../src/formatter/hook-index.ts","../../src/formatter/run-hook-tracker.ts","../../src/formatter/suite-builder.ts"],"sourcesContent":["export { default } from './formatter.js';\n","import { Formatter, type IFormatterOptions } from '@cucumber/cucumber';\nimport type { Envelope, GherkinDocument, Pickle, TestCase } from '@cucumber/messages';\n\nimport { resolveConfig, type QualflareCucumberOptions, type ResolvedFormatterConfig } from '../config/resolve-config.js';\nimport { QualflareHttpClient } from '../http/client.js';\nimport { logger } from '../shared/logger.js';\nimport { PACKAGE_VERSION } from '../config/version.js';\nimport { AttachmentBudget } from './attachment-budget.js';\nimport { AttemptTracker } from './attempt-tracker.js';\nimport { buildCase, type FinishedCase } from './case-builder.js';\nimport { buildCollectPayload } from './collect-builder.js';\nimport { GherkinIndex } from './gherkin-index.js';\nimport { buildHookIndex, type HookIndex } from './hook-index.js';\nimport { RunHookTracker } from './run-hook-tracker.js';\nimport { groupIntoSuites } from './suite-builder.js';\n\nexport default class QualflareCucumberFormatter extends Formatter {\n private readonly config: ResolvedFormatterConfig;\n private readonly gherkin = new GherkinIndex();\n private readonly hookIndex: HookIndex;\n private readonly pickleIndex = new Map<string, Pickle>();\n private readonly testCaseIndex = new Map<string, TestCase>();\n private readonly attachmentBudget: AttachmentBudget;\n private readonly attemptTracker: AttemptTracker;\n private readonly runHookTracker = new RunHookTracker();\n private readonly finishedCases: FinishedCase[] = [];\n\n constructor(options: IFormatterOptions) {\n super(options);\n // cucumber-js's `formatOptions` is untyped (`FormatOptions` has only an\n // index signature) — the shape is a contract between the user's config\n // and this formatter, not something cucumber-js itself validates.\n this.config = resolveConfig(options.parsedArgvOptions as QualflareCucumberOptions);\n this.hookIndex = buildHookIndex(options.supportCodeLibrary);\n this.attachmentBudget = new AttachmentBudget(this.config.maxTotalAttachmentBytes);\n this.attemptTracker = new AttemptTracker(this.hookIndex, this.gherkin, this.config, this.attachmentBudget);\n\n if (!this.config.enabled) {\n return;\n }\n options.eventBroadcaster.on('envelope', (envelope: Envelope) => this.onEnvelope(envelope));\n }\n\n private onEnvelope(envelope: Envelope): void {\n try {\n this.dispatch(envelope);\n } catch (err) {\n logger.error('failed to process a cucumber-js event:', err);\n }\n }\n\n private dispatch(envelope: Envelope): void {\n if (envelope.gherkinDocument) {\n this.onGherkinDocument(envelope.gherkinDocument);\n return;\n }\n if (envelope.pickle) {\n this.pickleIndex.set(envelope.pickle.id, envelope.pickle);\n return;\n }\n if (envelope.testCase) {\n this.testCaseIndex.set(envelope.testCase.id, envelope.testCase);\n return;\n }\n if (envelope.testCaseStarted) {\n const testCase = this.testCaseIndex.get(envelope.testCaseStarted.testCaseId);\n const pickle = testCase ? this.pickleIndex.get(testCase.pickleId) : undefined;\n if (testCase && pickle) {\n this.attemptTracker.begin(envelope.testCaseStarted, testCase, pickle);\n } else {\n // Should never happen under cucumber-js's documented message\n // ordering (testCase/pickle always precede testCaseStarted) — this\n // scenario attempt would otherwise be silently dropped from the\n // report with no signal at all, so warn rather than swallow it.\n logger.warn(\n `could not resolve testCase/pickle for testCaseStarted \"${envelope.testCaseStarted.id}\" — this scenario attempt will not be uploaded.`,\n );\n }\n return;\n }\n if (envelope.testStepStarted) {\n this.attemptTracker.stepStarted(envelope.testStepStarted);\n return;\n }\n if (envelope.testStepFinished) {\n this.attemptTracker.stepFinished(envelope.testStepFinished);\n return;\n }\n if (envelope.attachment) {\n this.attemptTracker.attachment(envelope.attachment);\n return;\n }\n if (envelope.testCaseFinished) {\n const finished = this.attemptTracker.finish(envelope.testCaseFinished);\n if (finished) {\n const pickle = this.pickleIndex.get(finished.pickleId);\n if (pickle) {\n this.finishedCases.push(buildCase(finished.uri, pickle, finished.collapsed, this.gherkin));\n } else {\n logger.warn(`could not resolve pickle \"${finished.pickleId}\" for a finished scenario — it will not be uploaded.`);\n }\n }\n return;\n }\n if (envelope.testRunHookStarted) {\n this.runHookTracker.start(envelope.testRunHookStarted);\n return;\n }\n if (envelope.testRunHookFinished) {\n this.runHookTracker.finish(envelope.testRunHookFinished, this.hookIndex);\n return;\n }\n }\n\n private onGherkinDocument(doc: GherkinDocument): void {\n this.gherkin.add(doc);\n }\n\n async finished(): Promise<void> {\n try {\n if (this.config.enabled) {\n await this.uploadResults();\n }\n } finally {\n await super.finished();\n }\n }\n\n private async uploadResults(): Promise<void> {\n const suites = groupIntoSuites(this.finishedCases, this.cwd, this.runHookTracker.buildSuite());\n if (suites.length === 0) {\n if (this.config.debug) {\n logger.debug('no scenarios reported — skipping upload.');\n }\n return;\n }\n const payload = buildCollectPayload(suites, this.config);\n const client = new QualflareHttpClient({\n endpoint: this.config.apiEndpoint,\n token: this.config.token,\n timeoutMs: this.config.timeoutMs,\n retry: this.config.retry,\n userAgent: `qualflare-cucumberjs/${PACKAGE_VERSION}`,\n debug: this.config.debug,\n });\n try {\n const result = await client.send(payload);\n if (this.config.debug) {\n logger.debug(`uploaded launch #${result.seq}.`);\n }\n } catch (err) {\n if (this.config.failOnUploadError) {\n throw err;\n }\n logger.error('failed to upload results to Qualflare:', err);\n }\n }\n}\n","import * as ciInfo from 'ci-info';\n\n/** Detected CI pipeline metadata, matching the wire contract's `ciProvider`/\n * `ciBuildNumber`/`ciRunUrl`/`ciPrNumber` fields exactly (`src/shared/types.ts`). */\nexport interface CiMetadata {\n ciProvider?: string;\n ciBuildNumber?: string;\n ciRunUrl?: string;\n ciPrNumber?: number;\n}\n\ninterface ProviderExtractor {\n detect: (env: NodeJS.ProcessEnv) => boolean;\n providerName: string;\n buildNumber?: (env: NodeJS.ProcessEnv) => string | undefined;\n runUrl?: (env: NodeJS.ProcessEnv) => string | undefined;\n prNumber?: (env: NodeJS.ProcessEnv) => number | undefined;\n}\n\nfunction parsePositiveInt(raw: string | undefined): number | undefined {\n if (!raw) {\n return undefined;\n }\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) && n >= 1 ? n : undefined;\n}\n\nfunction nonEmpty(raw: string | undefined): string | undefined {\n return raw && raw.length > 0 ? raw : undefined;\n}\n\n/** Explicit per-provider extraction for the fields `ci-info` doesn't\n * standardize (build number / run URL / PR number). Each `detect` reads\n * directly off the passed-in `env` — deliberately NOT delegating to\n * `ci-info`'s own per-vendor booleans (e.g. `ciInfo.GITHUB_ACTIONS`), which\n * are computed once against the real `process.env` at module-import time\n * and can't be re-evaluated against an injected env — see the module-level\n * comment on `detectCi` below. Checked in order; first match wins. */\nconst PROVIDERS: ProviderExtractor[] = [\n {\n detect: (env) => env.GITHUB_ACTIONS === 'true',\n providerName: 'GitHub Actions',\n buildNumber: (env) => nonEmpty(env.GITHUB_RUN_NUMBER),\n runUrl: (env) =>\n env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY && env.GITHUB_RUN_ID\n ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`\n : undefined,\n prNumber: (env) => {\n const match = /^refs\\/pull\\/(\\d+)\\/merge$/.exec(env.GITHUB_REF ?? '');\n return match ? parsePositiveInt(match[1]) : undefined;\n },\n },\n {\n detect: (env) => env.GITLAB_CI === 'true',\n providerName: 'GitLab CI',\n buildNumber: (env) => nonEmpty(env.CI_PIPELINE_IID),\n runUrl: (env) => nonEmpty(env.CI_PIPELINE_URL),\n prNumber: (env) => parsePositiveInt(env.CI_MERGE_REQUEST_IID),\n },\n {\n detect: (env) => env.CIRCLECI === 'true',\n providerName: 'CircleCI',\n buildNumber: (env) => nonEmpty(env.CIRCLE_BUILD_NUM),\n runUrl: (env) => nonEmpty(env.CIRCLE_BUILD_URL),\n prNumber: (env) => parsePositiveInt(env.CIRCLE_PR_NUMBER),\n },\n {\n detect: (env) => env.BUILDKITE === 'true',\n providerName: 'Buildkite',\n buildNumber: (env) => nonEmpty(env.BUILDKITE_BUILD_NUMBER),\n runUrl: (env) => nonEmpty(env.BUILDKITE_BUILD_URL),\n prNumber: (env) => {\n const raw = env.BUILDKITE_PULL_REQUEST;\n if (!raw || raw === 'false') {\n return undefined;\n }\n return parsePositiveInt(raw);\n },\n },\n {\n // Jenkins has no simple `JENKINS=true`-style flag; JENKINS_URL is always\n // set by the Jenkins agent and is the conventional detection signal.\n detect: (env) => Boolean(env.JENKINS_URL),\n providerName: 'Jenkins',\n buildNumber: (env) => nonEmpty(env.BUILD_NUMBER),\n runUrl: (env) => nonEmpty(env.BUILD_URL),\n // Jenkins has no standardized PR-number env var across its many PR\n // plugins (Multibranch, GitHub Branch Source, etc.) — deliberately\n // omitted rather than guessing at a plugin-specific variable.\n },\n {\n detect: (env) => env.TF_BUILD === 'True' || env.TF_BUILD === 'true',\n providerName: 'Azure Pipelines',\n buildNumber: (env) => nonEmpty(env.BUILD_BUILDID),\n runUrl: (env) => {\n const collectionUri = env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI;\n const project = env.SYSTEM_TEAMPROJECT;\n const buildId = env.BUILD_BUILDID;\n if (!collectionUri || !project || !buildId) {\n return undefined;\n }\n return `${collectionUri.replace(/\\/+$/, '')}/${encodeURIComponent(project)}/_build/results?buildId=${buildId}`;\n },\n prNumber: (env) => parsePositiveInt(env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER),\n },\n {\n detect: (env) => Boolean(env.BITBUCKET_BUILD_NUMBER),\n providerName: 'Bitbucket Pipelines',\n buildNumber: (env) => nonEmpty(env.BITBUCKET_BUILD_NUMBER),\n runUrl: (env) => {\n const origin = env.BITBUCKET_GIT_HTTP_ORIGIN;\n if (!origin) {\n return undefined;\n }\n const resultsId = env.BITBUCKET_PIPELINE_UUID ?? env.BITBUCKET_BUILD_NUMBER;\n return resultsId ? `${origin}/addon/pipelines/home#!/results/${resultsId}` : undefined;\n },\n prNumber: (env) => parsePositiveInt(env.BITBUCKET_PR_ID),\n },\n];\n\n/**\n * Detects CI pipeline metadata for the `Collect.ciProvider`/`ciBuildNumber`/\n * `ciRunUrl`/`ciPrNumber` fields.\n *\n * IMPORTANT, non-obvious limitation: the `env` parameter only governs the\n * `PROVIDERS` table above (this module's own, directly-env-reading logic).\n * The `ci-info` fallback below does NOT honor it — `ci-info`'s package source\n * computes `exports.name`/`exports.isCI` exactly once, against the REAL\n * `process.env`, at module-import time (`const env = process.env` at its top\n * level) — there is no API to re-evaluate it against a different env object.\n * This is a non-issue in production (this function is always called against\n * the real `process.env` there); `ci-detect.test.ts` exercises the `ci-info`\n * fallback path via `vi.stubEnv` + `vi.resetModules()` (forcing a fresh\n * `ci-info` evaluation) rather than via this function's `env` parameter.\n */\nexport function detectCi(env: NodeJS.ProcessEnv = process.env): CiMetadata {\n const provider = PROVIDERS.find((p) => p.detect(env));\n if (provider) {\n const result: CiMetadata = { ciProvider: provider.providerName };\n const buildNumber = provider.buildNumber?.(env);\n if (buildNumber !== undefined) result.ciBuildNumber = buildNumber;\n const runUrl = provider.runUrl?.(env);\n if (runUrl !== undefined) result.ciRunUrl = runUrl;\n const prNumber = provider.prNumber?.(env);\n if (prNumber !== undefined) result.ciPrNumber = prNumber;\n return result;\n }\n\n // Fallback: ci-info's ~70-provider detection gives us a free-text provider\n // name (the server's `ciProvider` field has no enum — an unrecognized\n // value is always accepted, per its doc comment in shared/types.ts) even\n // for providers our explicit table above doesn't cover a full\n // build-number/run-URL/PR-number extraction for.\n if (ciInfo.name) {\n return { ciProvider: ciInfo.name };\n }\n return {};\n}\n","import { execFileSync } from 'node:child_process';\n\n/** Auto-detected branch/commit — the bottom tier of `resolve-config.ts`'s\n * precedence chain (option > QUALFLARE_BRANCH/QF_BRANCH env > CI env vars >\n * local git subprocess > null). Does NOT include the QUALFLARE_BRANCH/\n * QF_BRANCH-style env aliases — those are resolved at a higher tier,\n * directly in `resolve-config.ts`, before this module is ever consulted. */\nexport interface GitInfo {\n branch?: string;\n commit?: string;\n}\n\n/** Injectable so tests never actually shell out. `stdio: ['ignore', 'pipe',\n * 'ignore']` suppresses git's own stderr chatter (e.g. \"fatal: not a git\n * repository\") from leaking into the CI log for what is, from this plugin's\n * perspective, an entirely expected, non-fatal outcome. */\nexport type ExecGit = (args: string[], cwd: string) => string;\n\nconst defaultExecGit: ExecGit = (args, cwd) =>\n execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });\n\nfunction firstEnv(env: NodeJS.ProcessEnv, ...names: string[]): string | undefined {\n for (const name of names) {\n const value = env[name];\n if (value) {\n return value;\n }\n }\n return undefined;\n}\n\nfunction detectBranchFromGit(exec: ExecGit, cwd: string): string | undefined {\n try {\n // Empty on detached HEAD (the `-q` flag suppresses the error and the\n // command still exits 0 with no output in that case on some git\n // versions, hence the explicit empty-string check as well as the\n // try/catch for versions that exit non-zero instead).\n const out = exec(['symbolic-ref', '--short', '-q', 'HEAD'], cwd).trim();\n return out || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction detectCommitFromGit(exec: ExecGit, cwd: string): string | undefined {\n try {\n const out = exec(['rev-parse', 'HEAD'], cwd).trim();\n return out || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Auto-detects branch/commit: CI-provider env vars first (cheap, no\n * subprocess), falling back to a local `git` subprocess only for whichever\n * of branch/commit the env vars didn't resolve — mirroring\n * `qualflare-cli/internal/config/config.go`'s `LoadFromEnv`\n * (`getFirstEnv(\"GIT_BRANCH\", \"GITHUB_REF_NAME\", \"CI_COMMIT_REF_NAME\",\n * \"BITBUCKET_BRANCH\")` / the equivalent commit chain) and its `DetectGit`'s\n * \"only shell out for what's actually missing\" behavior (BUG-39 in that\n * file: forking `git` on every CLI invocation, even `--help`, was wasteful —\n * the same reasoning applies here, this reporter should not fork two `git`\n * processes on every `cucumber-js` run when CI env vars already cover both\n * values, or when the caller already resolved both from options/env at a\n * higher precedence tier and doesn't need this module at all).\n */\nexport function detectGit(\n env: NodeJS.ProcessEnv = process.env,\n cwd: string = process.cwd(),\n exec: ExecGit = defaultExecGit,\n): GitInfo {\n const branch =\n firstEnv(env, 'GIT_BRANCH', 'GITHUB_REF_NAME', 'CI_COMMIT_REF_NAME', 'BITBUCKET_BRANCH') ??\n detectBranchFromGit(exec, cwd);\n const commit =\n firstEnv(env, 'GIT_COMMIT', 'GITHUB_SHA', 'CI_COMMIT_SHA', 'BITBUCKET_COMMIT') ??\n detectCommitFromGit(exec, cwd);\n\n const result: GitInfo = {};\n if (branch !== undefined) result.branch = branch;\n if (commit !== undefined) result.commit = commit;\n return result;\n}\n","import type { Platform } from '../shared/types.js';\nimport { detectCi, type CiMetadata } from './ci-detect.js';\nimport { detectGit, type GitInfo } from './git-detect.js';\n\n/** Options passed via the `--format-options`/`formatOptions` value the user\n * configures for `@qualflare/cucumberjs/formatter` (e.g. in `cucumber.js` /\n * `cucumber.json`). Every field here also has an environment-variable\n * override — see the precedence table in the README / plan. */\nexport interface QualflareCucumberOptions {\n token?: string;\n apiEndpoint?: string;\n environment?: string;\n language?: string;\n milestone?: number | null;\n branch?: string | null;\n commit?: string | null;\n platform?: Platform;\n framework?: string;\n os?: string;\n browser?: string;\n properties?: Record<string, string>;\n /** Max 64 chars. Free text, no enum — an unrecognized CI provider must\n * never be rejected. Auto-detected via `ci-detect.ts` when omitted. */\n ciProvider?: string;\n ciBuildNumber?: string;\n ciRunUrl?: string;\n ciPrNumber?: number;\n timeoutMs?: number;\n retry?: {\n max?: number;\n baseDelayMs?: number;\n maxDelayMs?: number;\n };\n failOnUploadError?: boolean;\n attachScreenshots?: boolean;\n /** Include `BeforeStep`/`AfterStep` hook executions as synthetic steps.\n * Off by default — these run once per Gherkin step and can multiply the\n * step count several-fold for suites with global per-step instrumentation\n * hooks (e.g. a screenshot-after-every-step hook), which is noisy as a\n * default but valuable as an explicit opt-in. */\n includeStepHooks?: boolean;\n maxAttachmentBytes?: number;\n maxTotalAttachmentBytes?: number;\n debug?: boolean;\n /** `false` fully disables accumulation/upload (a complete no-op) but the\n * formatter still no-ops cleanly rather than throwing. */\n enabled?: boolean;\n}\n\nexport interface ResolvedFormatterConfig {\n token: string;\n apiEndpoint: string;\n environment: string;\n language: string;\n milestone: number | null;\n branch: string | null;\n commit: string | null;\n platform: Platform;\n framework: string;\n os?: string;\n browser?: string;\n properties?: Record<string, string>;\n ciProvider?: string;\n ciBuildNumber?: string;\n ciRunUrl?: string;\n ciPrNumber?: number;\n timeoutMs: number;\n retry: { max: number; baseDelayMs: number; maxDelayMs: number };\n failOnUploadError: boolean;\n attachScreenshots: boolean;\n includeStepHooks: boolean;\n maxAttachmentBytes: number;\n maxTotalAttachmentBytes: number;\n debug: boolean;\n enabled: boolean;\n}\n\nfunction firstEnv(...names: string[]): string | undefined {\n for (const name of names) {\n const value = process.env[name];\n if (value !== undefined && value !== '') {\n return value;\n }\n }\n return undefined;\n}\n\nfunction envBool(...names: string[]): boolean | undefined {\n const raw = firstEnv(...names);\n if (raw === undefined) {\n return undefined;\n }\n return raw === 'true' || raw === '1';\n}\n\nfunction envInt(...names: string[]): number | undefined {\n const raw = firstEnv(...names);\n if (raw === undefined) {\n return undefined;\n }\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\n/** Thrown when a required value (currently only `token`) can't be resolved.\n * Deliberately thrown synchronously at formatter-construction time (i.e. as\n * soon as `cucumber-js` instantiates the formatter, before any scenario\n * runs) rather than deferred to the final upload — failing fast before any\n * scenario runs wastes far less CI time than discovering a misconfiguration\n * only at the very end of the run. */\nexport class QualflareConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'QualflareConfigError';\n }\n}\n\n/** Resolves the full formatter configuration from, in order: the explicit\n * `options` (the formatter's own `formatOptions`), then `QUALFLARE_*`\n * environment variables, then `QF_*` (compat alias with the existing Go\n * CLI, where an equivalent exists), then a hardcoded default.\n *\n * Branch/commit precedence: `options.branch`/`.commit` (including an\n * explicit `null`, which is respected as \"no auto-detection wanted\" rather\n * than triggering the fallback tiers below it) > `QUALFLARE_BRANCH`/\n * `QF_BRANCH` env (and the commit equivalent) > CI-provider env vars > a\n * local `git` subprocess (`git-detect.ts`) > `null`. The subprocess tier is\n * skipped entirely — no `git` process is forked — once both branch and\n * commit are already resolved from options/env, mirroring\n * `qualflare-cli/internal/config/config.go`'s `DetectGit`'s early return.\n *\n * CI-metadata precedence (`ciProvider`/`ciBuildNumber`/`ciRunUrl`/\n * `ciPrNumber`): the corresponding `options.ci*` field, else `ci-detect.ts`'s\n * auto-detection (per-provider extraction table, falling back to the\n * `ci-info` package's ~70-provider free-text name).\n *\n * `deps` lets tests inject fake `detectGit`/`detectCi` implementations\n * instead of the real ones (which shell out to `git` and read the real\n * `process.env`/`ci-info` module state) — defaults to the real detectors,\n * so every production call site (the formatter's constructor calls\n * `resolveConfig(options)` with no second argument) is unaffected.\n */\nexport function resolveConfig(\n options: QualflareCucumberOptions,\n deps: { detectGit?: () => GitInfo; detectCi?: () => CiMetadata } = {},\n): ResolvedFormatterConfig {\n const doDetectGit = deps.detectGit ?? detectGit;\n const doDetectCi = deps.detectCi ?? detectCi;\n\n const enabled = options.enabled ?? envBool('QUALFLARE_ENABLED') ?? true;\n\n const token = options.token ?? firstEnv('QUALFLARE_TOKEN', 'QF_TOKEN') ?? '';\n if (enabled && token === '') {\n throw new QualflareConfigError(\n 'qualflare-cucumberjs: no token configured. Set the `token` format option or the ' +\n 'QUALFLARE_TOKEN (or QF_TOKEN) environment variable, or pass `enabled: false` to disable ' +\n 'this formatter.',\n );\n }\n\n const milestoneRaw = options.milestone !== undefined ? options.milestone : envInt('QUALFLARE_MILESTONE', 'QF_MILESTONE');\n const milestone = milestoneRaw !== undefined && milestoneRaw !== null && milestoneRaw >= 1 ? milestoneRaw : null;\n\n const envBranch = firstEnv('QUALFLARE_BRANCH', 'QF_BRANCH');\n const envCommit = firstEnv('QUALFLARE_COMMIT', 'QF_COMMIT');\n const needsGitDetection =\n (options.branch === undefined && envBranch === undefined) ||\n (options.commit === undefined && envCommit === undefined);\n const detectedGit = needsGitDetection ? doDetectGit() : {};\n\n const branch = options.branch !== undefined ? options.branch : (envBranch ?? detectedGit.branch ?? null);\n const commit = options.commit !== undefined ? options.commit : (envCommit ?? detectedGit.commit ?? null);\n\n const detectedCi = doDetectCi();\n const ciProvider = options.ciProvider ?? detectedCi.ciProvider;\n const ciBuildNumber = options.ciBuildNumber ?? detectedCi.ciBuildNumber;\n const ciRunUrl = options.ciRunUrl ?? detectedCi.ciRunUrl;\n const ciPrNumber = options.ciPrNumber ?? detectedCi.ciPrNumber;\n\n return {\n token,\n apiEndpoint: options.apiEndpoint ?? firstEnv('QUALFLARE_API_ENDPOINT') ?? 'https://api.qualflare.com',\n // `||` (truthy check), not `??`, for these three REQUIRED-non-empty wire\n // fields — an explicit `''` option must not silently win over the\n // default (the server rejects an empty `environment`, and since\n // `failOnUploadError` defaults `false`, that would fail the entire\n // upload with no visible error by default). Ported verbatim from\n // qualflare-cypress, where this was found via deep adversarial review.\n environment: (options.environment || undefined) ?? firstEnv('QUALFLARE_ENVIRONMENT', 'QF_ENVIRONMENT') ?? 'development',\n language: (options.language || undefined) ?? firstEnv('QUALFLARE_LANGUAGE', 'QF_LANGUAGE') ?? 'en-US',\n milestone,\n branch,\n commit,\n platform: options.platform ?? 'web',\n framework: options.framework || 'cucumber',\n os: options.os,\n browser: options.browser,\n properties: options.properties,\n ciProvider,\n ciBuildNumber,\n ciRunUrl,\n ciPrNumber,\n timeoutMs: options.timeoutMs ?? envInt('QUALFLARE_TIMEOUT_MS') ?? 120_000,\n retry: {\n max: options.retry?.max ?? envInt('QUALFLARE_RETRY_MAX', 'QF_RETRY_MAX') ?? 3,\n baseDelayMs: options.retry?.baseDelayMs ?? envInt('QUALFLARE_RETRY_BASE_DELAY_MS') ?? 1000,\n maxDelayMs: options.retry?.maxDelayMs ?? envInt('QUALFLARE_RETRY_MAX_DELAY_MS') ?? 30_000,\n },\n failOnUploadError: options.failOnUploadError ?? envBool('QUALFLARE_FAIL_ON_UPLOAD_ERROR') ?? false,\n attachScreenshots: options.attachScreenshots ?? envBool('QUALFLARE_ATTACH_SCREENSHOTS') ?? true,\n includeStepHooks: options.includeStepHooks ?? envBool('QUALFLARE_INCLUDE_STEP_HOOKS') ?? false,\n maxAttachmentBytes: options.maxAttachmentBytes ?? envInt('QUALFLARE_MAX_ATTACHMENT_BYTES') ?? 1_500_000,\n maxTotalAttachmentBytes:\n options.maxTotalAttachmentBytes ?? envInt('QUALFLARE_MAX_TOTAL_ATTACHMENT_BYTES') ?? 750_000,\n debug: options.debug ?? envBool('QUALFLARE_DEBUG', 'QF_DEBUG') ?? false,\n enabled,\n };\n}\n","import { request } from 'undici';\n\nimport {\n HEADER_ACCEPT,\n HEADER_CONTENT_TYPE,\n HEADER_IDEMPOTENCY_KEY,\n HEADER_TOKEN,\n HEADER_USER_AGENT,\n} from '../shared/constants.js';\nimport { logger } from '../shared/logger.js';\nimport type { ApiErrorResponse, Collect, CollectResult } from '../shared/types.js';\nimport { computeDelay } from './backoff.js';\nimport { buildApiError, QualflareApiError } from './errors.js';\nimport { newIdempotencyKey } from './idempotency.js';\n\nexport interface RetryOptions {\n max: number;\n baseDelayMs: number;\n maxDelayMs: number;\n}\n\nexport interface SendOptions {\n endpoint: string;\n token: string;\n timeoutMs: number;\n retry: RetryOptions;\n userAgent: string;\n debug: boolean;\n}\n\n/** HTTP status codes worth retrying: a transient/transport-level condition\n * that a later attempt might succeed at. Mirrors\n * `qualflare-cli/internal/adapters/http/client.go`'s `AddRetryConditions`\n * exactly. 400/401/403/404/413/422 are all client-error conditions a retry\n * cannot fix. */\nconst RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503, 504]);\n\nfunction redactToken(token: string): string {\n return token.length > 0 ? '***REDACTED***' : '(none)';\n}\n\n/** POSTs a `Collect` payload to `/api/v1/collect`, mirroring the Go CLI's\n * resty-based HTTP client behavior: a stable per-call Idempotency-Key reused\n * across retries, exponential backoff with jitter on transport errors and\n * 429/500/502/503/504, disabled redirects (the auth token must never leak to\n * a different host via a redirect hop), and a generous default timeout that\n * leaves real headroom over the server's own ~30s /collect transaction\n * budget. */\nexport class QualflareHttpClient {\n constructor(private readonly opts: SendOptions) {}\n\n async send(collect: Collect): Promise<CollectResult> {\n const url = `${this.opts.endpoint.replace(/\\/+$/, '')}/api/v1/collect`;\n const idempotencyKey = newIdempotencyKey();\n const body = JSON.stringify(collect);\n const maxAttempts = Math.max(1, this.opts.retry.max + 1);\n\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {\n if (this.opts.debug) {\n logger.debug(\n `POST ${url} (attempt ${attempt}/${maxAttempts}, QF_TOKEN: ${redactToken(this.opts.token)})`,\n );\n }\n\n let statusCode: number;\n let responseBody: string;\n let responseHeaders: Record<string, string | string[] | undefined>;\n try {\n const res = await request(url, {\n method: 'POST',\n headers: {\n [HEADER_TOKEN]: this.opts.token,\n [HEADER_CONTENT_TYPE]: 'application/json',\n [HEADER_ACCEPT]: 'application/json',\n [HEADER_USER_AGENT]: this.opts.userAgent,\n [HEADER_IDEMPOTENCY_KEY]: idempotencyKey,\n },\n body,\n maxRedirections: 0,\n signal: AbortSignal.timeout(this.opts.timeoutMs),\n });\n statusCode = res.statusCode;\n responseHeaders = res.headers;\n responseBody = await res.body.text();\n } catch (err) {\n lastError = err;\n if (this.opts.debug) {\n logger.debug(`attempt ${attempt} transport error: ${(err as Error)?.message ?? err}`);\n }\n if (attempt < maxAttempts) {\n await sleep(computeDelay(attempt, this.opts.retry.baseDelayMs, this.opts.retry.maxDelayMs));\n continue;\n }\n throw new QualflareApiError({\n message: `failed to send request to ${url}`,\n cause: err,\n });\n }\n\n if (this.opts.debug) {\n logger.debug(`attempt ${attempt} response: ${statusCode}`);\n }\n\n if (statusCode >= 200 && statusCode < 300) {\n return parseSuccess(responseBody);\n }\n\n const parsedError = parseErrorBody(responseBody);\n\n if (!RETRYABLE_STATUS_CODES.has(statusCode) || attempt === maxAttempts) {\n throw buildApiError(statusCode, parsedError);\n }\n\n const retryAfterMs = parseRetryAfter(responseHeaders['retry-after']);\n const delay = computeDelay(\n attempt,\n this.opts.retry.baseDelayMs,\n this.opts.retry.maxDelayMs,\n retryAfterMs,\n );\n if (this.opts.debug) {\n logger.debug(`retrying after ${Math.round(delay)}ms (status ${statusCode})`);\n }\n await sleep(delay);\n }\n\n // Unreachable in practice (the loop always returns or throws), but keeps\n // the function's return type honest without a non-null assertion.\n throw lastError instanceof Error\n ? lastError\n : new QualflareApiError({ message: 'request failed for an unknown reason' });\n }\n}\n\nfunction parseSuccess(responseBody: string): CollectResult {\n try {\n const parsed = JSON.parse(responseBody) as CollectResult;\n if (typeof parsed.seq !== 'number') {\n throw new Error('response body missing numeric \"seq\"');\n }\n return parsed;\n } catch (err) {\n throw new QualflareApiError({\n message: 'server returned a success status but an unparseable body',\n cause: err,\n });\n }\n}\n\nfunction parseErrorBody(responseBody: string): ApiErrorResponse | undefined {\n if (!responseBody) {\n return undefined;\n }\n try {\n return JSON.parse(responseBody) as ApiErrorResponse;\n } catch {\n return undefined;\n }\n}\n\nfunction parseRetryAfter(value: string | string[] | undefined): number | undefined {\n const raw = Array.isArray(value) ? value[0] : value;\n if (!raw) {\n return undefined;\n }\n // Retry-After is either a number of seconds or an HTTP-date; only the\n // (far more common, and simpler to trust) numeric-seconds form is honored\n // here — an HTTP-date value is ignored and falls back to the computed\n // exponential-backoff delay instead.\n const seconds = Number(raw);\n return Number.isFinite(seconds) && seconds >= 0 ? seconds * 1000 : undefined;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * Shared constants used across the formatter and the author-facing runtime\n * API.\n */\n\n/** Reserved `World.attach()` media type used to smuggle structured\n * `qualflare.*()` calls (label/tag/step/etc.) from step-definition and hook\n * code back to the formatter process — the only data channel CucumberJS\n * gives user code back to a running formatter. The formatter's attachment\n * handler recognizes this exact media type and replays the message as a\n * model mutation instead of rendering it as a literal attachment. */\nexport const RESERVED_MESSAGE_MEDIA_TYPE = 'application/vnd.qualflare.message+json';\n\n/** HTTP headers used against `/api/v1/collect`. */\nexport const HEADER_TOKEN = 'QF_TOKEN';\nexport const HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key';\nexport const HEADER_CONTENT_TYPE = 'Content-Type';\nexport const HEADER_ACCEPT = 'Accept';\nexport const HEADER_USER_AGENT = 'User-Agent';\n\n/** Server-side caps this client should respect defensively (see\n * `api-service/internal/core/domain/launch/launch.go`). */\nexport const MAX_SUITES_PER_LAUNCH = 2000;\nexport const MAX_CASES_PER_SUITE = 5000;\nexport const MAX_STEPS_PER_CASE = 1000;\nexport const MAX_PARAMETERS_PER_STEP = 50;\nexport const MAX_ATTACHMENTS_PER_CASE = 50;\nexport const MAX_LABELS_PER_CASE = 100;\nexport const MAX_LINKS_PER_CASE = 20;\nexport const MAX_TAGS_PER_CASE = 64;\nexport const MAX_TAG_LENGTH = 255;\nexport const MAX_IDEMPOTENCY_KEY_CHARS = 255;\n\n/** Client-side SOFT cap on steps recorded per scenario attempt — well under\n * the server's 1000-per-case hard cap (`MAX_STEPS_PER_CASE`). Once hit,\n * further steps within that attempt are dropped (with a one-time warning),\n * not queued and truncated later. */\nexport const MAX_STEPS_PER_TEST_ATTEMPT = 300;\n","/**\n * A minimal logger writing to stderr. Deliberately avoids stdout, since\n * that's typically `cucumber-js`'s own test-output stream and shouldn't be\n * polluted with reporter diagnostics.\n */\n\nconst PREFIX = '[qualflare-cucumberjs]';\n\nexport const logger = {\n debug(...args: unknown[]): void {\n console.debug(PREFIX, ...args);\n },\n info(...args: unknown[]): void {\n console.log(PREFIX, ...args);\n },\n warn(...args: unknown[]): void {\n console.warn(PREFIX, ...args);\n },\n error(...args: unknown[]): void {\n console.error(PREFIX, ...args);\n },\n};\n","/**\n * Computes the delay before retry attempt `attempt` (1-based: the delay\n * before the SECOND request, i.e. after the first failure). Exponential\n * backoff with full jitter, capped at `maxDelayMs` — intent-parity with the\n * Go CLI's resty-based exponential 1s->30s backoff (not necessarily\n * byte-identical; resty's internal jitter algorithm isn't something worth\n * reverse-engineering).\n *\n * When the server sent a `Retry-After` value (in milliseconds, already\n * parsed by the caller), the returned delay is never shorter than that —\n * honoring it is a defensible improvement the Go CLI doesn't make, cheap to\n * add, and directly useful against the known \"questionable retry-on-429/503\n * behavior server-side\" gap noted in api-service's own code-quality review.\n */\nexport function computeDelay(\n attempt: number,\n baseDelayMs: number,\n maxDelayMs: number,\n retryAfterMs?: number,\n): number {\n const exponential = baseDelayMs * 2 ** Math.max(0, attempt - 1);\n const jittered = Math.random() * Math.min(exponential, maxDelayMs);\n const floor = retryAfterMs !== undefined ? retryAfterMs : 0;\n // Bounded by maxDelayMs even when honoring a server Retry-After: a\n // pathological/misconfigured server value should never be able to hang a\n // CI job indefinitely.\n return Math.min(Math.max(jittered, floor), maxDelayMs);\n}\n","import type { ApiErrorResponse, ApiFieldError } from '../shared/types.js';\n\n/** CLI-side friendly hints for a few well-known server error codes — only\n * used as a fallback when the server sent no `message` of its own. Mirrors\n * `qualflare-cli/internal/adapters/http/client.go`'s `getUserFriendlyMessage`.\n * `common.resource_not_found` is deliberately NOT aliased to a specific\n * resource here — that's the generic 404 code and aliasing it caused a\n * documented bug in the Go CLI (every 404 rendering as \"Language not found\"). */\nfunction friendlyHint(code: string | undefined): string | undefined {\n switch (code) {\n case 'environment.not_found':\n return 'Environment not found. Check the `environment` option or create it in Qualflare.';\n case 'milestone.not_found':\n return 'Milestone not found. Check the `milestone` option or its sequence number in Qualflare.';\n case 'common.validation_failed':\n return 'Validation failed. Check the request data below.';\n default:\n return undefined;\n }\n}\n\nfunction actionHint(statusCode: number | undefined): string | undefined {\n switch (statusCode) {\n case 401:\n return 'the configured token is missing or invalid — check `token`/QUALFLARE_TOKEN';\n case 403:\n return 'the token lacks access to this project';\n case 402:\n return 'a plan limit was reached — check your Qualflare subscription';\n default:\n return undefined;\n }\n}\n\n/** Renders the server's per-field validation errors into one readable line.\n * This is a real, confirmed gap the existing Go CLI has (it parses `fields`\n * off the wire but never renders them) — this package renders them from\n * day one instead of just showing the top-level `message`. */\nfunction renderFields(fields: ApiFieldError[] | undefined): string | undefined {\n if (!fields || fields.length === 0) {\n return undefined;\n }\n return fields\n .map((f) => {\n const rule = f.rule ? ` (${f.rule})` : '';\n const msg = f.message ? `: ${f.message}` : '';\n return `${f.field}${rule}${msg}`;\n })\n .join('; ');\n}\n\nexport interface QualflareApiErrorInit {\n message: string;\n code?: string;\n statusCode?: number;\n requestId?: string;\n fields?: ApiFieldError[];\n cause?: unknown;\n}\n\n/** Thrown by `QualflareHttpClient.send()` on any terminal (non-retried, or\n * retries-exhausted) failure. Carries everything needed to log something\n * actionable without the caller having to know the wire error shape. */\nexport class QualflareApiError extends Error {\n readonly code?: string;\n readonly statusCode?: number;\n readonly requestId?: string;\n readonly fields?: ApiFieldError[];\n\n constructor(init: QualflareApiErrorInit) {\n const parts = [init.message];\n const fieldsRendered = renderFields(init.fields);\n if (fieldsRendered) {\n parts.push(`fields: ${fieldsRendered}`);\n }\n const hint = actionHint(init.statusCode);\n if (hint) {\n parts.push(`(${hint})`);\n }\n if (init.requestId) {\n parts.push(`[request_id: ${init.requestId}]`);\n }\n super(parts.join(' — '), init.cause !== undefined ? { cause: init.cause } : undefined);\n this.name = 'QualflareApiError';\n this.code = init.code;\n this.statusCode = init.statusCode;\n this.requestId = init.requestId;\n this.fields = init.fields;\n }\n}\n\n/** Parses the server's `{code, message, fields, request_id}` envelope (also\n * tolerating the legacy `{error}` key) into a `QualflareApiError`. `body`\n * may be `undefined` if the response wasn't valid JSON at all. */\nexport function buildApiError(statusCode: number, body: ApiErrorResponse | undefined): QualflareApiError {\n const code = body?.code;\n const message = body?.message || friendlyHint(code) || body?.error || `request failed with status ${statusCode}`;\n return new QualflareApiError({\n message,\n code,\n statusCode,\n requestId: body?.request_id,\n fields: body?.fields,\n });\n}\n","import { randomUUID } from 'node:crypto';\n\nimport { MAX_IDEMPOTENCY_KEY_CHARS } from '../shared/constants.js';\n\n/**\n * Returns a fresh RFC 4122 v4 UUID string for use as an `Idempotency-Key`.\n * Call ONCE per logical `send()` attempt and reuse the same value across\n * every retry of that call — the server resolves a retried key to the\n * already-created launch, so a mid-flight 5xx retry cannot double-create.\n * Mirrors `qualflare-cli/internal/adapters/http/client.go`'s\n * `newIdempotencyKey`, using the platform RNG instead of a hand-rolled one.\n */\nexport function newIdempotencyKey(): string {\n const key = randomUUID();\n // A UUID is always 36 chars, well under the server's 255-char cap — this\n // assertion exists purely to fail loudly if that invariant ever changes\n // upstream, not because truncation here would ever be reachable.\n if (key.length > MAX_IDEMPOTENCY_KEY_CHARS) {\n return key.slice(0, MAX_IDEMPOTENCY_KEY_CHARS);\n }\n return key;\n}\n","// `__PACKAGE_VERSION__` is injected at build time by tsup's `define` option\n// (see tsup.config.ts) from package.json's `version` field. Deliberately\n// NOT read at runtime via `import.meta.url` + `createRequire` — that breaks\n// the CJS build output (`import.meta` is empty/unavailable once esbuild\n// compiles to CommonJS), which is exactly the class of dual-CJS/ESM-package\n// bug a build-time constant sidesteps entirely. Under Vitest (which never\n// goes through tsup), `vitest.config.ts` defines the same constant so this\n// module behaves identically in tests and in the built package.\ndeclare const __PACKAGE_VERSION__: string;\n\nexport const PACKAGE_VERSION: string = __PACKAGE_VERSION__;\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { logger } from '../shared/logger.js';\nimport type { Attachment } from '../shared/types.js';\n\n/** Extensions/mime-prefixes this reporter must never attach — Qualflare has\n * no video/blob-attachment support yet (a separate, unbuilt backend\n * feature), the same constraint `@qualflare/cypress` documents. */\nconst VIDEO_EXTENSIONS = new Set(['.mp4', '.webm', '.mov', '.avi', '.mkv']);\n\nexport interface AttachmentBudgetConfig {\n attachScreenshots: boolean;\n maxAttachmentBytes: number;\n maxTotalAttachmentBytes: number;\n}\n\n/** One `World.attach()` call (real user attachment, or `qualflare.attachment\n * ()`/`attachmentFromFile()`), not yet resolved into a wire `Attachment`.\n * Exactly one of `content`/`path` is set — `content` for cucumber-js's\n * native in-memory delivery (a Buffer/base64 string, the common case for\n * real `World.attach()` calls) or `qualflare.attachment()`; `path` only for\n * `qualflare.attachmentFromFile()`, cucumber-js itself never delivers a bare\n * file path. */\nexport interface PendingAttachment {\n name: string;\n mimeType?: string;\n stepIndex?: number;\n /** Base64-encoded. */\n content?: string;\n path?: string;\n}\n\n/**\n * Tracks cumulative attached bytes across the whole `cucumber-js` process\n * (one instance per formatter, reused across every attachment resolved),\n * so the final POST doesn't silently exceed the request body limit. Ported\n * verbatim in spirit from `@qualflare/cypress`'s `AttachmentBudget`.\n */\nexport class AttachmentBudget {\n private used = 0;\n\n constructor(private readonly maxTotalBytes: number) {}\n\n /** Atomically checks-and-reserves `bytes` against the remaining budget.\n * Returns false (reserving nothing) if it would exceed the total. */\n tryReserve(bytes: number): boolean {\n if (this.used + bytes > this.maxTotalBytes) {\n return false;\n }\n this.used += bytes;\n return true;\n }\n\n get usedBytes(): number {\n return this.used;\n }\n}\n\ntype ReadResult = { skipped: false; content: string } | { skipped: true; reason: string };\n\nfunction isVideoLike(mimeType: string | undefined, filePath: string | undefined): boolean {\n if (mimeType?.toLowerCase().startsWith('video/')) {\n return true;\n }\n if (filePath && VIDEO_EXTENSIONS.has(path.extname(filePath).toLowerCase())) {\n return true;\n }\n return false;\n}\n\nfunction readAttachmentFile(filePath: string, maxAttachmentBytes: number, budget: AttachmentBudget): ReadResult {\n let size: number;\n try {\n // Stat BEFORE reading — an oversized file must never be loaded into\n // memory just to discover it should be skipped.\n size = fs.statSync(filePath).size;\n } catch (err) {\n return { skipped: true, reason: `could not stat file: ${(err as Error).message}` };\n }\n if (size > maxAttachmentBytes) {\n return {\n skipped: true,\n reason: `${size} bytes exceeds the configured per-attachment cap of ${maxAttachmentBytes} bytes`,\n };\n }\n if (!budget.tryReserve(size)) {\n return {\n skipped: true,\n reason: `would exceed this run's total attachment budget (${budget.usedBytes} bytes already used)`,\n };\n }\n try {\n const content = fs.readFileSync(filePath).toString('base64');\n return { skipped: false, content };\n } catch (err) {\n return { skipped: true, reason: `could not read file: ${(err as Error).message}` };\n }\n}\n\n/**\n * Resolves one pending attachment into a wire `Attachment`, or `undefined`\n * if it should be skipped entirely (per the plan's resolved decision — an\n * oversized/over-budget/video attachment is dropped, not degraded to a\n * contentless stub, since the server's `path` field is explicitly\n * informational/never-fetched). Unlike `@qualflare/cypress`'s\n * `resolveAttachments()` (which batch-resolves a Case's whole array at\n * case-finish time), this resolves one attachment at a time as its\n * `attachment` envelope arrives — matching cucumber-js's per-envelope\n * event stream.\n */\nexport function resolvePendingAttachment(\n pending: PendingAttachment,\n config: AttachmentBudgetConfig,\n budget: AttachmentBudget,\n): Attachment | undefined {\n if (!config.attachScreenshots) {\n return undefined;\n }\n if (isVideoLike(pending.mimeType, pending.path)) {\n logger.warn(\n `refusing to attach \"${pending.name}\": video attachments are not supported yet ` +\n `(${pending.path ?? pending.mimeType ?? 'unknown'}).`,\n );\n return undefined;\n }\n if (pending.content !== undefined) {\n const bytes = Buffer.byteLength(pending.content, 'base64');\n if (bytes > config.maxAttachmentBytes) {\n logger.warn(\n `skipping attachment \"${pending.name}\": ${bytes} bytes exceeds the configured per-attachment cap of ${config.maxAttachmentBytes} bytes`,\n );\n return undefined;\n }\n if (!budget.tryReserve(bytes)) {\n logger.warn(\n `skipping attachment \"${pending.name}\": would exceed this run's total attachment budget (${budget.usedBytes} bytes already used)`,\n );\n return undefined;\n }\n return { name: pending.name, mimeType: pending.mimeType, content: pending.content, stepIndex: pending.stepIndex };\n }\n if (pending.path) {\n const result = readAttachmentFile(pending.path, config.maxAttachmentBytes, budget);\n if (result.skipped) {\n logger.warn(`skipping attachment \"${pending.name}\" (${pending.path}): ${result.reason}`);\n return undefined;\n }\n return {\n name: pending.name,\n mimeType: pending.mimeType,\n content: result.content,\n path: pending.path,\n stepIndex: pending.stepIndex,\n };\n }\n return undefined;\n}\n","import {\n getWorstTestStepResult,\n type Attachment as MessageAttachment,\n type Pickle,\n type TestCase,\n type TestCaseFinished,\n type TestCaseStarted,\n type TestStepFinished,\n type TestStepResult,\n type TestStepStarted,\n} from '@cucumber/messages';\n\nimport { RESERVED_MESSAGE_MEDIA_TYPE, MAX_STEPS_PER_TEST_ATTEMPT } from '../shared/constants.js';\nimport { messageDurationToNs } from '../shared/duration.js';\nimport { logger } from '../shared/logger.js';\nimport type { ManualStepRecord, RuntimeMessage } from '../runtime/message-types.js';\nimport type { Attachment, CasePriority, Label, Link, Step } from '../shared/types.js';\nimport { AttachmentBudget, resolvePendingAttachment, type AttachmentBudgetConfig } from './attachment-budget.js';\nimport { collapseAttempts, type AttemptSnapshot, type CollapsedResult } from './case-builder.js';\nimport type { GherkinIndex } from './gherkin-index.js';\nimport type { HookIndex } from './hook-index.js';\nimport { mapHookStep, mapPickleStep, mapStatus } from './step-mapper.js';\n\nexport interface FinishedAttempts {\n uri: string;\n pickleId: string;\n collapsed: CollapsedResult;\n}\n\ninterface AttemptRecord {\n testCase: TestCase;\n pickle: Pickle;\n attempt: number;\n startedAtMs: number;\n steps: Step[];\n stepResults: TestStepResult[];\n /** Maps a `TestStep.id` to its index in `steps`, once finished — used to\n * correlate a same-step attachment's `stepIndex` on the wire. */\n stepIndexByTestStepId: Map<string, number>;\n currentTestStepId?: string;\n manualSteps: ManualStepRecord[];\n manualStepStack: number[];\n labels: Label[];\n links: Link[];\n tags: string[];\n description?: string;\n priority?: CasePriority;\n properties: Record<string, string>;\n attachments: Attachment[];\n stepCapWarned: boolean;\n}\n\n/**\n * The retry-safe core. Two lookup layers, both required because\n * cucumber-js's *grouping* key (a logical scenario, stable across retries —\n * `testCase.id`) and its *live-event-addressing* key (one specific attempt\n * — `testCaseStarted.id`) are genuinely different fields on the wire.\n * `testCaseFinished.willBeRetried === false` is the sole authoritative\n * \"this scenario is really done\" signal — no hash-based grouping anywhere\n * (the deliberate fix for `allure-framework/allure-js#625`/`#1502`, both\n * real bugs caused by Allure's content-hash retry/outline-row grouping).\n */\nexport class AttemptTracker {\n private readonly byTestCaseId = new Map<string, AttemptRecord[]>();\n private readonly byTestCaseStartedId = new Map<string, AttemptRecord>();\n\n constructor(\n private readonly hookIndex: HookIndex,\n private readonly gherkin: GherkinIndex,\n private readonly config: { includeStepHooks: boolean } & AttachmentBudgetConfig,\n private readonly attachmentBudget: AttachmentBudget,\n ) {}\n\n begin(e: TestCaseStarted, testCase: TestCase, pickle: Pickle): void {\n const record: AttemptRecord = {\n testCase,\n pickle,\n attempt: e.attempt,\n startedAtMs: timestampMs(e.timestamp),\n steps: [],\n stepResults: [],\n stepIndexByTestStepId: new Map(),\n manualSteps: [],\n manualStepStack: [],\n labels: [],\n links: [],\n tags: [],\n properties: {},\n attachments: [],\n stepCapWarned: false,\n };\n this.byTestCaseStartedId.set(e.id, record);\n const attempts = this.byTestCaseId.get(testCase.id) ?? [];\n attempts.push(record);\n this.byTestCaseId.set(testCase.id, attempts);\n }\n\n stepStarted(e: TestStepStarted): void {\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n if (!record) {\n return;\n }\n record.currentTestStepId = e.testStepId;\n }\n\n stepFinished(e: TestStepFinished): void {\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n if (!record) {\n return;\n }\n record.currentTestStepId = undefined;\n record.stepResults.push(e.testStepResult);\n\n const testStep = record.testCase.testSteps.find((s) => s.id === e.testStepId);\n if (!testStep) {\n return;\n }\n\n let step: Step | undefined;\n if (testStep.pickleStepId) {\n const pickleStep = record.pickle.steps.find((s) => s.id === testStep.pickleStepId);\n if (pickleStep) {\n step = mapPickleStep(record.pickle.uri, pickleStep, e.testStepResult, this.gherkin);\n }\n } else if (testStep.hookId) {\n const hook = this.hookIndex.get(testStep.hookId);\n if (hook && (hook.kind === 'before' || hook.kind === 'after')) {\n step = mapHookStep(hook, e.testStepResult);\n } else if (hook && (hook.kind === 'beforeStep' || hook.kind === 'afterStep') && this.config.includeStepHooks) {\n // Deliberately NOT nested under the pickle step it wraps: BeforeStep\n // fires (and is pushed here) BEFORE that step, so \"the most recently\n // pushed step\" would be the PREVIOUS, unrelated step at that point —\n // an earlier version of this nested BeforeStep under the wrong\n // parent. AfterStep could be nested correctly (its wrapped step is\n // already pushed by the time it fires), but nesting one and not the\n // other would be inconsistent, so both stay flat, root-level steps —\n // still informative via their position immediately adjacent to the\n // step they wrap in the flat, chronologically-ordered array.\n step = mapHookStep(hook, e.testStepResult);\n }\n }\n\n if (!step) {\n return;\n }\n if (record.steps.length >= MAX_STEPS_PER_TEST_ATTEMPT) {\n if (!record.stepCapWarned) {\n record.stepCapWarned = true;\n logger.warn(\n `reached the ${MAX_STEPS_PER_TEST_ATTEMPT}-step-per-attempt cap — further steps in this scenario attempt will not be uploaded.`,\n );\n }\n return;\n }\n record.stepIndexByTestStepId.set(e.testStepId, record.steps.length);\n record.steps.push(step);\n }\n\n /** Handles both a real user `World.attach()` call (becomes a wire\n * `Attachment`) and a `qualflare.*()` reserved-media-type message\n * (unwrapped and applied as a model mutation instead). */\n attachment(e: MessageAttachment): void {\n if (!e.testCaseStartedId) {\n // A BeforeAll/AfterAll-scoped attachment (`testRunHookStartedId` set\n // instead) — there is no Case to attach it to; see `run-hook-tracker\n // .ts`'s doc comment for why BeforeAll/AfterAll attachments are out of\n // scope for v1.\n return;\n }\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n if (!record) {\n return;\n }\n\n if (e.mediaType === RESERVED_MESSAGE_MEDIA_TYPE) {\n let message: RuntimeMessage;\n try {\n message = JSON.parse(e.contentEncoding === 'BASE64' ? Buffer.from(e.body, 'base64').toString('utf8') : e.body);\n } catch {\n logger.warn('received a malformed qualflare runtime message — ignoring it.');\n return;\n }\n this.applyRuntimeMessage(record, message, e.testStepId);\n return;\n }\n\n const stepIndex = this.resolveStepIndex(record, e.testStepId);\n const content = e.contentEncoding === 'BASE64' ? e.body : Buffer.from(e.body, 'utf8').toString('base64');\n const resolved = resolvePendingAttachment(\n { name: e.fileName || 'attachment', mimeType: e.mediaType, content, stepIndex },\n this.config,\n this.attachmentBudget,\n );\n if (resolved) {\n record.attachments.push(resolved);\n }\n }\n\n /** Resolves which step index an attachment (real or `qualflare.*()`\n * message) belongs to. `stepIndexByTestStepId` only gets an entry once a\n * step is FINISHED — but `World.attach()` (the only channel available,\n * used by both a real user attachment and every `qualflare.*()` call) is\n * always called from WITHIN a step's body, i.e. strictly BETWEEN that\n * step's `testStepStarted` and `testStepFinished`. So the map lookup\n * alone can never resolve the step that's currently attaching — this was\n * a real bug found in self-review, caught by a unit test that attaches\n * mid-step instead of only after it finishes. While a step is in flight,\n * `record.steps.length` (the array's CURRENT length, before that step has\n * been pushed) is exactly the index it will occupy once it does finish —\n * single-threaded, event-ordered execution guarantees nothing else can be\n * pushed in between. */\n private resolveStepIndex(record: AttemptRecord, testStepId: string | undefined): number | undefined {\n if (testStepId === undefined) {\n return undefined;\n }\n if (record.currentTestStepId === testStepId) {\n return record.steps.length;\n }\n return record.stepIndexByTestStepId.get(testStepId);\n }\n\n private applyRuntimeMessage(record: AttemptRecord, message: RuntimeMessage, testStepId: string | undefined): void {\n switch (message.type) {\n case 'label':\n record.labels.push({ name: message.name, value: message.value });\n return;\n case 'link':\n record.links.push({ type: message.linkType ?? 'custom', name: message.name, url: message.url });\n return;\n case 'tag':\n record.tags.push(...message.tags);\n return;\n case 'description':\n record.description = message.text;\n return;\n case 'priority':\n record.priority = message.value;\n return;\n case 'parameter': {\n const openStepIndex = record.manualStepStack[record.manualStepStack.length - 1];\n if (openStepIndex !== undefined) {\n const step = record.manualSteps[openStepIndex]!;\n step.parameters = step.parameters ?? [];\n step.parameters.push({ name: message.name, value: message.value, masked: message.masked });\n } else {\n record.properties[message.name] = message.value ?? '';\n }\n return;\n }\n case 'attachment': {\n const stepIndex = testStepId !== undefined ? record.stepIndexByTestStepId.get(testStepId) : undefined;\n const resolved = resolvePendingAttachment(\n { name: message.name, mimeType: message.mimeType, content: message.contentBase64, stepIndex },\n this.config,\n this.attachmentBudget,\n );\n if (resolved) {\n record.attachments.push(resolved);\n }\n return;\n }\n case 'attachment_from_file': {\n const stepIndex = testStepId !== undefined ? record.stepIndexByTestStepId.get(testStepId) : undefined;\n const resolved = resolvePendingAttachment(\n { name: message.name, mimeType: message.mimeType, path: message.path, stepIndex },\n this.config,\n this.attachmentBudget,\n );\n if (resolved) {\n record.attachments.push(resolved);\n }\n return;\n }\n case 'step_start': {\n const parentIndex = record.manualStepStack.length > 0 ? record.manualStepStack[record.manualStepStack.length - 1] : undefined;\n const step: ManualStepRecord = { name: message.name, status: 'passed', startedAt: message.timestamp, parentIndex };\n record.manualStepStack.push(record.manualSteps.length);\n record.manualSteps.push(step);\n return;\n }\n case 'step_stop': {\n const openIndex = record.manualStepStack.pop();\n if (openIndex === undefined) {\n return;\n }\n const step = record.manualSteps[openIndex]!;\n step.status = message.status;\n step.error = message.error;\n step.durationMs = Math.max(0, message.timestamp - step.startedAt);\n return;\n }\n }\n }\n\n /** Returns the collapsed result once all attempts of this logical\n * scenario have arrived, or `undefined` if more attempts are coming\n * (`willBeRetried === true`). */\n finish(e: TestCaseFinished): FinishedAttempts | undefined {\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n this.byTestCaseStartedId.delete(e.testCaseStartedId);\n if (!record) {\n return undefined;\n }\n if (e.willBeRetried) {\n return undefined;\n }\n\n const testCaseId = record.testCase.id;\n const attempts = this.byTestCaseId.get(testCaseId) ?? [record];\n this.byTestCaseId.delete(testCaseId);\n\n const snapshots: AttemptSnapshot[] = attempts.map((a) => {\n const worst = a.stepResults.length > 0 ? getWorstTestStepResult(a.stepResults) : undefined;\n const duration = a.stepResults.reduce((sum, r) => sum + messageDurationToNs(r.duration), 0);\n return {\n status: worst ? mapStatus(worst.status) : 'passed',\n duration,\n // `message` first — see `step-mapper.ts`'s `formatError()` doc\n // comment for why (verified empirically across the peer-dependency\n // range; `exception.stackTrace` is not version-safe).\n error: worst?.message || worst?.exception?.stackTrace || worst?.exception?.message,\n steps: a.steps,\n manualSteps: a.manualSteps,\n labels: a.labels,\n links: a.links,\n tags: a.tags,\n description: a.description,\n priority: a.priority,\n properties: a.properties,\n attachments: a.attachments,\n };\n });\n\n return {\n uri: record.pickle.uri,\n pickleId: record.testCase.pickleId,\n collapsed: collapseAttempts(snapshots),\n };\n }\n}\n\nfunction timestampMs(ts: { seconds: number; nanos: number }): number {\n return ts.seconds * 1000 + Math.floor(ts.nanos / 1_000_000);\n}\n","import type { NanosecondDuration } from './types.js';\n\nconst NS_PER_MS = 1_000_000;\nconst NS_PER_SECOND = 1_000_000_000;\n\n/**\n * Converts a plain millisecond duration (e.g. from `Date.now()` deltas used\n * by manual `qualflare.step()` timing) into the wire format's raw-nanosecond\n * integer (see `NanosecondDuration` in ./types.ts).\n *\n * Rounds (not truncates) so fractional-ms input doesn't lose precision by\n * always rounding toward zero. Negative input is clamped to 0 — a negative\n * duration is never legitimate and silently clamping is safer for an\n * ingest payload than throwing and aborting an otherwise-good report.\n */\nexport function msToNs(ms: number): NanosecondDuration {\n if (!Number.isFinite(ms) || ms <= 0) {\n return 0;\n }\n return Math.round(ms * NS_PER_MS);\n}\n\n/**\n * Converts a `@cucumber/messages` structured `Duration` ({seconds, nanos})\n * into the wire format's raw-nanosecond integer, without round-tripping\n * through milliseconds (which would lose sub-millisecond precision Cucumber\n * already reports natively). Negative/malformed input is clamped to 0, same\n * defensive posture as `msToNs`.\n */\nexport function messageDurationToNs(duration: { seconds: number; nanos: number } | undefined): NanosecondDuration {\n if (!duration || !Number.isFinite(duration.seconds) || !Number.isFinite(duration.nanos)) {\n return 0;\n }\n const total = duration.seconds * NS_PER_SECOND + duration.nanos;\n return total > 0 ? Math.round(total) : 0;\n}\n","import type { Pickle } from '@cucumber/messages';\n\nimport { MAX_TAGS_PER_CASE } from '../shared/constants.js';\nimport { msToNs } from '../shared/duration.js';\nimport type { ManualStepRecord } from '../runtime/message-types.js';\nimport type { Attachment, Case, CasePriority, CaseStatus, Label, Link, NanosecondDuration, Step } from '../shared/types.js';\nimport type { GherkinIndex } from './gherkin-index.js';\n\n/** One attempt of a scenario — cucumber-js's `--retry` re-runs the same\n * pickle from scratch (fresh World, all hooks/steps rerun); each attempt\n * (`testCaseStarted`→...→`testCaseFinished`) produces one of these. Ported\n * from `@qualflare/cypress`'s `AttemptSnapshot`/`collapseAttempts` — same\n * \"final attempt wins for content, but count+sum across all attempts\"\n * rules — fed by envelope-derived data here instead of Mocha-derived data. */\nexport interface AttemptSnapshot {\n status: CaseStatus;\n /** NANOSECONDS — already summed across this attempt's real step results;\n * see `attempt-tracker.ts`. No ms round-trip, unlike the Cypress version:\n * Cucumber's own `TestStepResult.duration` is nanosecond-precision. */\n duration: NanosecondDuration;\n error?: string;\n /** Real Gherkin + hook steps, already wire-shaped by `step-mapper.ts`. */\n steps: Step[];\n manualSteps: ManualStepRecord[];\n labels: Label[];\n links: Link[];\n /** Dynamically added via `qualflare.tag()` during this attempt — merged\n * with the pickle's own static `@tag`s separately, in `case-builder.ts`'s\n * `buildCase()`, since those are the same across every attempt. */\n tags: string[];\n description?: string;\n priority?: CasePriority;\n properties: Record<string, string>;\n attachments: Attachment[];\n}\n\nexport interface CollapsedResult {\n status: CaseStatus;\n /** NANOSECONDS — sum of every attempt (reflects true CI wall-clock cost,\n * not just the final attempt's duration). */\n duration: NanosecondDuration;\n retryCount: number;\n isFlaky: boolean;\n error?: string;\n /** Only the FINAL attempt's steps — an abandoned (retried) attempt's step\n * trace would misrepresent a single execution as if the same commands ran\n * twice, so earlier attempts' steps are discarded, never merged. */\n steps?: Step[];\n labels: Label[];\n links: Link[];\n tags: string[];\n description?: string;\n priority?: CasePriority;\n properties: Record<string, string>;\n attachments: Attachment[];\n}\n\n/** Appends `manualSteps` (from `qualflare.step()`, indices/`parentIndex`\n * valid only relative to each other) after `realSteps` (Gherkin + hook\n * steps, indices/`parentIndex` valid only relative to each other) into one\n * combined, correctly-indexed `Step[]`. Ported verbatim in spirit from\n * `@qualflare/cypress`'s `combineSteps()`. */\nfunction combineSteps(realSteps: Step[], manualSteps: ManualStepRecord[]): Step[] | undefined {\n if (realSteps.length === 0 && manualSteps.length === 0) {\n return undefined;\n }\n const offsetManual: Step[] = manualSteps.map((record) => {\n const step: Step = {\n name: record.name,\n status: record.status,\n duration: msToNs(record.durationMs ?? 0),\n };\n if (record.error) step.error = record.error;\n if (record.parentIndex !== undefined) step.parentIndex = record.parentIndex + realSteps.length;\n if (record.parameters && record.parameters.length > 0) step.parameters = record.parameters;\n return step;\n });\n return [...realSteps, ...offsetManual];\n}\n\n/**\n * Collapses every attempt of one logical scenario (grouped by cucumber-js's\n * own stable `testCase.id`, NOT a content hash — see `attempt-tracker.ts`)\n * into the single `Case` this reporter uploads. `willBeRetried === false` on\n * the final `testCaseFinished` is the caller's signal that all attempts have\n * arrived; this function only does the collapse.\n */\nexport function collapseAttempts(attempts: AttemptSnapshot[]): CollapsedResult {\n if (attempts.length === 0) {\n throw new Error('collapseAttempts: at least one attempt is required');\n }\n const final = attempts[attempts.length - 1]!;\n const retryCount = attempts.length - 1;\n const isFlaky = retryCount > 0 && final.status === 'passed' && attempts.some((a) => a.status !== 'passed');\n const duration = attempts.reduce((sum, a) => sum + a.duration, 0);\n\n return {\n status: final.status,\n duration,\n retryCount,\n isFlaky,\n error: final.status === 'passed' ? undefined : final.error,\n steps: combineSteps(final.steps, final.manualSteps),\n labels: final.labels,\n links: final.links,\n tags: final.tags,\n description: final.description,\n priority: final.priority,\n properties: final.properties,\n attachments: final.attachments,\n };\n}\n\nexport interface FinishedCase {\n uri: string;\n case: Case;\n}\n\n/** Assembles the final wire `Case` for one finished (all-attempts-collapsed)\n * scenario. */\nexport function buildCase(uri: string, pickle: Pickle, collapsed: CollapsedResult, gherkin: GherkinIndex): FinishedCase {\n const entry = gherkin.get(uri);\n // `pickle.astNodeIds` is `[scenarioId]` for a plain Scenario, or\n // `[scenarioId, exampleRowId]` for one row of a Scenario Outline — the\n // scenario's own AST id is always first.\n const scenarioId = pickle.astNodeIds[0];\n const scenarioEntry = scenarioId ? entry?.scenarioById.get(scenarioId) : undefined;\n const featureName = entry?.featureName;\n const ruleName = scenarioEntry?.ruleName;\n const className = ruleName ? `${featureName ?? ''} > ${ruleName}` : featureName;\n\n const labels: Label[] = [...collapsed.labels];\n if (featureName) {\n labels.push({ name: 'feature', value: featureName });\n }\n if (ruleName) {\n labels.push({ name: 'rule', value: ruleName });\n }\n\n const properties: Record<string, string> = { ...examplesRowProperties(scenarioEntry, pickle), ...collapsed.properties };\n\n // `pickle.tags` already has Feature/Rule `@tag`s inherited/flattened onto\n // every scenario by cucumber-js itself — no manual inheritance needed.\n const staticTags = pickle.tags.map((t) => t.name.replace(/^@/, ''));\n const tags = [...new Set([...staticTags, ...collapsed.tags])].slice(0, MAX_TAGS_PER_CASE);\n\n const kase: Case = {\n // Stable across runs (unchanged unless the file is edited), and\n // includes the source line specifically so two identically-named\n // Scenarios in one feature file can never collide — the exact bug\n // that's open upstream in allure-js (allure-framework/allure-js#1502).\n id: `${uri}:${pickle.location?.line ?? 0}#${pickle.name}`,\n name: pickle.name,\n status: collapsed.status,\n duration: collapsed.duration,\n retryCount: collapsed.retryCount || undefined,\n isFlaky: collapsed.isFlaky || undefined,\n error: collapsed.error,\n tags: tags.length > 0 ? tags : undefined,\n steps: collapsed.steps,\n labels: labels.length > 0 ? labels : undefined,\n links: collapsed.links.length > 0 ? collapsed.links : undefined,\n // `||`, not `??`: cucumber-js's `Scenario.description` is always a\n // defined string, `''` when the Gherkin source has none — `??` would\n // never fall through and every scenario without one would send an\n // empty-string description instead of omitting the field (found via a\n // real tarball-installed smoke test, not just reasoning about types).\n description: collapsed.description || scenarioEntry?.scenario.description || undefined,\n priority: collapsed.priority,\n properties: Object.keys(properties).length > 0 ? properties : undefined,\n attachments: collapsed.attachments.length > 0 ? collapsed.attachments : undefined,\n };\n if (className) {\n kase.className = className;\n }\n return { uri, case: kase };\n}\n\n/** For a Scenario Outline row, folds that row's concrete Examples values\n * into `Case.properties` (`{columnName: cellValue}`) — the same AST\n * `tableBody`-correlation technique verified in `allure-cucumberjs`'s real\n * source. Every row's values show up on that row's Case automatically, with\n * zero extra author effort. */\nfunction examplesRowProperties(\n scenarioEntry: { scenario: { examples: readonly { tableHeader?: { cells: readonly { value: string }[] }; tableBody: readonly { id: string; cells: readonly { value: string }[] }[] }[] } } | undefined,\n pickle: Pickle,\n): Record<string, string> {\n if (!scenarioEntry) {\n return {};\n }\n const rowAstIds = new Set(pickle.astNodeIds);\n for (const examples of scenarioEntry.scenario.examples) {\n const header = examples.tableHeader?.cells;\n if (!header) {\n continue;\n }\n const row = examples.tableBody.find((r) => rowAstIds.has(r.id));\n if (!row) {\n continue;\n }\n const properties: Record<string, string> = {};\n header.forEach((cell, i) => {\n const value = row.cells[i]?.value;\n if (cell.value && value !== undefined) {\n properties[cell.value] = value;\n }\n });\n return properties;\n }\n return {};\n}\n","import { TestStepResultStatus, type PickleStep, type PickleStepArgument, type TestStepResult } from '@cucumber/messages';\n\nimport { messageDurationToNs } from '../shared/duration.js';\nimport type { CaseStatus, Parameter, Step } from '../shared/types.js';\nimport type { GherkinIndex } from './gherkin-index.js';\nimport type { HookInfo } from './hook-index.js';\n\n/** Maps cucumber-js's step/scenario result status onto the wire contract's\n * `CaseStatus` vocabulary. `UNDEFINED` (no matching step definition) and\n * `AMBIGUOUS` (more than one matching definition) are both authoring/config\n * problems rather than a real pass/fail outcome — `'error'` is the closest\n * semantic fit in the wire vocabulary, distinct from a genuine `'failed'`\n * assertion. */\nexport function mapStatus(status: TestStepResultStatus): CaseStatus {\n switch (status) {\n case TestStepResultStatus.PASSED:\n return 'passed';\n case TestStepResultStatus.FAILED:\n return 'failed';\n case TestStepResultStatus.SKIPPED:\n return 'skipped';\n case TestStepResultStatus.PENDING:\n return 'pending';\n case TestStepResultStatus.UNDEFINED:\n case TestStepResultStatus.AMBIGUOUS:\n case TestStepResultStatus.UNKNOWN:\n default:\n return 'error';\n }\n}\n\n/** `TestStepResult.message` is the version-safe field to prefer: verified\n * empirically (real spawned runs, not just docs) that it reliably contains\n * the full \"Error: <message>\\n at ...\" text on both the peer floor\n * (cucumber-js 10.9.0) and the latest (13.2.1) — `exception.stackTrace`\n * does NOT: on 10.9.0 it's stack-frames only, with no message text at all,\n * while on 13.2.1 it happens to duplicate the full combined text. Preferring\n * `stackTrace` first (an earlier version of this function did) silently\n * produced a message-less error on 10.9.0 — caught by running the real CI\n * version matrix locally before trusting it, not by reasoning about the\n * schema alone. */\nfunction formatError(result: TestStepResult): string | undefined {\n return result.message || result.exception?.stackTrace || result.exception?.message;\n}\n\n/** Doc Strings and Data Tables have no dedicated field on the wire `Step`\n * contract (confirmed against `launch.go` — the only structured-payload\n * slot on a step is the flat `parameters` list) — encoded as one Parameter\n * each, documented as a workaround in `docs/LIMITATIONS.md`. A Data Table is\n * JSON-stringified as one Parameter rather than exploded into one Parameter\n * per cell, to avoid risking `MAX_PARAMETERS_PER_STEP` on a large table. */\nexport function pickleStepArgumentToParameters(argument: PickleStepArgument | undefined): Parameter[] | undefined {\n if (!argument) {\n return undefined;\n }\n const params: Parameter[] = [];\n if (argument.docString) {\n params.push({ name: 'docString', value: argument.docString.content });\n }\n if (argument.dataTable) {\n const rows = argument.dataTable.rows.map((row) => row.cells.map((cell) => cell.value));\n params.push({ name: 'dataTable', value: JSON.stringify(rows) });\n }\n return params.length > 0 ? params : undefined;\n}\n\n/** Builds a wire `Step` for a real Gherkin (Given/When/Then/And/But) step. */\nexport function mapPickleStep(\n uri: string,\n pickleStep: PickleStep,\n result: TestStepResult,\n gherkin: GherkinIndex,\n): Step {\n const resolved = gherkin.resolveKeyword(uri, pickleStep.astNodeIds);\n const step: Step = {\n name: pickleStep.text,\n status: mapStatus(result.status),\n duration: messageDurationToNs(result.duration),\n };\n if (resolved?.keyword) {\n step.keyword = resolved.keyword.trim();\n }\n const error = formatError(result);\n if (error) {\n step.error = error;\n }\n const parameters = pickleStepArgumentToParameters(pickleStep.argument);\n if (parameters) {\n step.parameters = parameters;\n }\n return step;\n}\n\n/** Builds a synthetic wire `Step` for a `Before`/`After` (or, when enabled,\n * `BeforeStep`/`AfterStep`) hook execution — see design decision (a) in the\n * plan: hooks are folded directly into the flat/nested `Step[]` rather than\n * needing a separate \"fixture\" model concept the way Allure's richer model\n * requires, since our wire contract has no such concept anyway. */\nconst HOOK_LABELS: Record<HookInfo['kind'], string> = {\n before: 'Before',\n after: 'After',\n beforeStep: 'BeforeStep',\n afterStep: 'AfterStep',\n beforeAll: 'BeforeAll',\n afterAll: 'AfterAll',\n};\n\nexport function mapHookStep(hook: HookInfo, result: TestStepResult): Step {\n // A distinct label per hook kind (not just \"Before\"/\"After\" for\n // everything) so a step-level hook is distinguishable from a case-level\n // one in the uploaded data when `includeStepHooks` is enabled.\n const label = HOOK_LABELS[hook.kind];\n const step: Step = {\n name: hook.name || `${label} hook`,\n keyword: label,\n status: mapStatus(result.status),\n duration: messageDurationToNs(result.duration),\n };\n const error = formatError(result);\n if (error) {\n step.error = error;\n }\n return step;\n}\n","import * as os from 'node:os';\n\nimport { PACKAGE_VERSION } from '../config/version.js';\nimport type { ResolvedFormatterConfig } from '../config/resolve-config.js';\nimport type { Collect, Suite } from '../shared/types.js';\n\nfunction resolveOs(config: ResolvedFormatterConfig): string {\n if (config.os) {\n return config.os;\n }\n return `${os.type()} ${os.release()}`;\n}\n\n/**\n * Assembles the final `Collect` payload from every finished `Suite`, at\n * `finished()`. CI metadata and branch/commit auto-detection are already\n * fully resolved by `resolve-config.ts` — this function just reads the\n * resolved config through, it does not call `ci-detect.ts`/`git-detect.ts`\n * itself. Unlike `@qualflare/cypress`'s version, there is no `BrowserInfo`\n * parameter — cucumber-js has no browser context of its own (unless a user\n * pairs it with a browser driver, which is outside this reporter's own\n * knowledge), so `browser` is config-only and `os` falls back to\n * `os.type()/os.release()`.\n */\nexport function buildCollectPayload(suites: Suite[], config: ResolvedFormatterConfig): Collect {\n return {\n framework: config.framework,\n platform: config.platform,\n os: resolveOs(config),\n browser: config.browser ?? '',\n branch: config.branch,\n commit: config.commit,\n environment: config.environment,\n language: config.language,\n milestone: config.milestone,\n metadata: {\n version: PACKAGE_VERSION,\n timestamp: new Date().toISOString(),\n cliName: 'qualflare-cucumberjs',\n },\n properties: config.properties,\n suites,\n ciProvider: config.ciProvider,\n ciBuildNumber: config.ciBuildNumber,\n ciRunUrl: config.ciRunUrl,\n ciPrNumber: config.ciPrNumber,\n };\n}\n","import type { GherkinDocument, Scenario } from '@cucumber/messages';\n\ninterface ScenarioEntry {\n scenario: Scenario;\n ruleName?: string;\n}\n\ninterface FeatureEntry {\n featureName?: string;\n /** AST step id -> literal Gherkin keyword (\"Given \"/\"When \"/\"Then \"/\"And \"/\n * \"But \", with cucumber-js's own trailing space) + step text. Background\n * steps are indexed here too — cucumber-js already merges Background\n * steps into every scenario's compiled `Pickle.steps[]` itself, so no\n * separate handling is needed; a Background step's AST id just needs to\n * resolve here like any other. */\n stepMap: Map<string, { keyword: string; text: string }>;\n scenarioById: Map<string, ScenarioEntry>;\n}\n\n/**\n * Indexes each `gherkinDocument` envelope (one per feature file) so\n * `case-builder.ts`/`step-mapper.ts` can resolve, per pickle: the Feature\n * name, an optional `Rule:` name, the Scenario AST node (for its\n * `examples[]`, used for Scenario Outline row correlation), and literal\n * Given/When/Then/And/But keyword text for each step (the compiled\n * `PickleStep.type` only gives a coarse Context/Action/Outcome/Unknown\n * classification, not the literal keyword).\n */\nexport class GherkinIndex {\n private readonly byUri = new Map<string, FeatureEntry>();\n\n add(doc: GherkinDocument): void {\n if (!doc.uri || !doc.feature) {\n return;\n }\n const entry: FeatureEntry = {\n featureName: doc.feature.name || undefined,\n stepMap: new Map(),\n scenarioById: new Map(),\n };\n for (const child of doc.feature.children) {\n if (child.background) {\n this.indexSteps(entry, child.background.steps);\n }\n if (child.scenario) {\n entry.scenarioById.set(child.scenario.id, { scenario: child.scenario });\n this.indexSteps(entry, child.scenario.steps);\n }\n if (child.rule) {\n for (const ruleChild of child.rule.children) {\n if (ruleChild.background) {\n this.indexSteps(entry, ruleChild.background.steps);\n }\n if (ruleChild.scenario) {\n entry.scenarioById.set(ruleChild.scenario.id, {\n scenario: ruleChild.scenario,\n ruleName: child.rule.name || undefined,\n });\n this.indexSteps(entry, ruleChild.scenario.steps);\n }\n }\n }\n }\n this.byUri.set(doc.uri, entry);\n }\n\n private indexSteps(entry: FeatureEntry, steps: readonly { id: string; keyword: string; text: string }[]): void {\n for (const step of steps) {\n entry.stepMap.set(step.id, { keyword: step.keyword, text: step.text });\n }\n }\n\n get(uri: string): FeatureEntry | undefined {\n return this.byUri.get(uri);\n }\n\n /** Resolves the literal Given/When/Then/And/But keyword text for a\n * compiled `PickleStep`, by walking its `astNodeIds` back to the first\n * one present in this feature's `stepMap` (a Background step referenced\n * by a scenario has exactly one AST id; a step reusing a parameter type\n * from an outline row can have more than one — the first match is always\n * the step's own defining AST node). */\n resolveKeyword(uri: string, astNodeIds: readonly string[]): { keyword: string; text: string } | undefined {\n const entry = this.byUri.get(uri);\n if (!entry) {\n return undefined;\n }\n for (const id of astNodeIds) {\n const found = entry.stepMap.get(id);\n if (found) {\n return found;\n }\n }\n return undefined;\n }\n}\n","import type { IFormatterOptions } from '@cucumber/cucumber';\n\n// `SupportCodeLibrary` itself isn't re-exported from the package's public\n// entry point (only reachable via a deep `lib/**` import, which the\n// package's exports map only allows for `require`, not `import` — this\n// package is ESM-first). Deriving the type via indexed access on the\n// already-public `IFormatterOptions` avoids that deep import entirely.\ntype SupportCodeLibrary = IFormatterOptions['supportCodeLibrary'];\n\nexport type HookKind = 'before' | 'after' | 'beforeStep' | 'afterStep' | 'beforeAll' | 'afterAll';\n\nexport interface HookInfo {\n kind: HookKind;\n name?: string;\n}\n\nexport type HookIndex = ReadonlyMap<string, HookInfo>;\n\n/**\n * Resolves a hook's kind from `supportCodeLibrary`'s six definition-array\n * fields, NOT the envelope's `Hook.type` — that field was only added in\n * cucumber-js 11.2.0 (confirmed via the project's own CHANGELOG), which is\n * after this package's peer floor (`>=10.8.0`). This is also exactly the\n * mechanism `allure-cucumberjs`'s real source uses for `Before`/`After`\n * (though it never indexes the `TestStep`/`TestRunHook` collections at all,\n * which is why it silently drops `BeforeStep`/`AfterStep` and never handles\n * `BeforeAll`/`AfterAll` — this package intentionally indexes all six).\n */\nexport function buildHookIndex(supportCodeLibrary: SupportCodeLibrary): HookIndex {\n const index = new Map<string, HookInfo>();\n for (const def of supportCodeLibrary.beforeTestCaseHookDefinitions) {\n index.set(def.id, { kind: 'before', name: def.name || undefined });\n }\n for (const def of supportCodeLibrary.afterTestCaseHookDefinitions) {\n index.set(def.id, { kind: 'after', name: def.name || undefined });\n }\n for (const def of supportCodeLibrary.beforeTestStepHookDefinitions) {\n index.set(def.id, { kind: 'beforeStep' });\n }\n for (const def of supportCodeLibrary.afterTestStepHookDefinitions) {\n index.set(def.id, { kind: 'afterStep' });\n }\n for (const def of supportCodeLibrary.beforeTestRunHookDefinitions) {\n index.set(def.id, { kind: 'beforeAll' });\n }\n for (const def of supportCodeLibrary.afterTestRunHookDefinitions) {\n index.set(def.id, { kind: 'afterAll' });\n }\n return index;\n}\n","import type { TestRunHookFinished, TestRunHookStarted } from '@cucumber/messages';\n\nimport { messageDurationToNs } from '../shared/duration.js';\nimport type { Case } from '../shared/types.js';\nimport type { HookIndex } from './hook-index.js';\nimport { mapStatus } from './step-mapper.js';\n\n/**\n * `BeforeAll`/`AfterAll` run outside any test case (`testRunHookStarted`/\n * `Finished`, never a `Case`) — `allure-cucumberjs` drops these entirely\n * (confirmed by reading its real source: no case for them anywhere in its\n * envelope-dispatch switch). This tracker does not: a FAILED run-hook\n * becomes a synthetic `Case` in a `(global hooks)` Suite (design decision\n * (b) in the plan); a passing one produces nothing — a passing `BeforeAll`\n * has no useful signal to report, and would be pure noise on every run.\n */\nexport class RunHookTracker {\n /** `testRunHookStartedId` -> the hook it started, so `finish()` can look\n * up its kind/name. `TestRunHookFinished.result.duration` already gives\n * an accurate duration directly — no start/finish timestamp delta needed. */\n private readonly started = new Map<string, string>();\n private readonly failed: Case[] = [];\n\n start(e: TestRunHookStarted): void {\n this.started.set(e.id, e.hookId);\n }\n\n finish(e: TestRunHookFinished, hookIndex: HookIndex): void {\n const hookId = this.started.get(e.testRunHookStartedId);\n this.started.delete(e.testRunHookStartedId);\n const status = mapStatus(e.result.status);\n if (status === 'passed' || status === 'skipped') {\n return;\n }\n const hook = hookId ? hookIndex.get(hookId) : undefined;\n const label = hook?.kind === 'afterAll' ? 'AfterAll hook' : 'BeforeAll hook';\n this.failed.push({\n id: `global-hook:${e.testRunHookStartedId}`,\n name: hook?.name || label,\n status,\n duration: messageDurationToNs(e.result.duration),\n // `result.message` first — see `step-mapper.ts`'s `formatError()` doc\n // comment for why (verified empirically to be the version-safe field\n // across the peer-dependency range; `exception.stackTrace` is not).\n error: e.result.message || e.result.exception?.stackTrace || e.result.exception?.message,\n });\n }\n\n /** Returns `undefined` if no run-hook failed — see the class doc comment\n * for why a passing BeforeAll/AfterAll produces no Case at all. */\n buildSuite(): { name: string; category: 'bdd'; duration: number; cases: Case[] } | undefined {\n if (this.failed.length === 0) {\n return undefined;\n }\n return {\n name: '(global hooks)',\n category: 'bdd',\n duration: this.failed.reduce((sum, c) => sum + c.duration, 0),\n cases: this.failed,\n };\n }\n}\n","import * as path from 'node:path';\n\nimport { MAX_CASES_PER_SUITE, MAX_SUITES_PER_LAUNCH } from '../shared/constants.js';\nimport { logger } from '../shared/logger.js';\nimport type { Case, Suite } from '../shared/types.js';\nimport type { FinishedCase } from './case-builder.js';\n\n/**\n * Groups every finished Case into one `Suite` per `.feature` file. Unlike\n * `@qualflare/cypress` (which can batch incrementally, one spec file at a\n * time, since Cypress runs specs sequentially in one process),\n * cucumber-js can interleave scenarios from different feature files even\n * without `--parallel`, and always runs the formatter in the coordinator\n * process either way (worker *threads* under `--parallel` ship envelopes\n * back to it) — so grouping happens once, at `finished()`, over the whole\n * run's flat list of finished cases.\n */\nexport function groupIntoSuites(cases: FinishedCase[], cwd: string, extraSuite?: Suite): Suite[] {\n const byUri = new Map<string, Case[]>();\n for (const { uri, case: kase } of cases) {\n const bucket = byUri.get(uri);\n if (bucket) {\n bucket.push(kase);\n } else {\n byUri.set(uri, [kase]);\n }\n }\n\n const suites: Suite[] = [];\n for (const [uri, kases] of byUri) {\n if (kases.length > MAX_CASES_PER_SUITE) {\n logger.warn(\n `feature file \"${uri}\" reported ${kases.length} scenarios — only the first ${MAX_CASES_PER_SUITE} will be uploaded (server cap).`,\n );\n }\n suites.push({\n name: relativizeUri(uri, cwd),\n category: 'bdd',\n duration: kases.reduce((sum, c) => sum + c.duration, 0),\n cases: kases.slice(0, MAX_CASES_PER_SUITE),\n });\n }\n\n if (extraSuite) {\n suites.push(extraSuite);\n }\n\n if (suites.length > MAX_SUITES_PER_LAUNCH) {\n logger.warn(\n `this run reported ${suites.length} feature-file suites — only the first ${MAX_SUITES_PER_LAUNCH} will be uploaded (server cap).`,\n );\n }\n return suites.slice(0, MAX_SUITES_PER_LAUNCH);\n}\n\n/** cucumber-js's own `pickle.uri` is already relative to the invocation cwd\n * in the common case (confirmed empirically: a real run reports e.g.\n * `\"features/passing.feature\"`, not an absolute path or a `file://` URL) —\n * this only normalizes the rarer absolute-path/URL forms down to the same\n * shape. */\nfunction relativizeUri(uri: string, cwd: string): string {\n let normalized = uri;\n if (normalized.startsWith('file://')) {\n normalized = new URL(normalized).pathname;\n }\n if (path.isAbsolute(normalized)) {\n normalized = path.relative(cwd, normalized);\n }\n return normalized.split(path.sep).join('/');\n}\n"],"mappings":"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","names":["name","firstEnv","name","import_messages","path"]}
|
|
1
|
+
{"version":3,"sources":["../../src/formatter/index.ts","../../src/formatter/formatter.ts","../../src/shared/constants.ts","../../src/config/ci-detect.ts","../../src/config/git-detect.ts","../../src/config/resolve-config.ts","../../src/shared/logger.ts","../../src/formatter/attachment-budget.ts","../../src/formatter/video-uploader.ts","../../src/formatter/attempt-tracker.ts","../../src/shared/duration.ts","../../src/formatter/case-builder.ts","../../src/formatter/step-mapper.ts","../../src/formatter/collect-builder.ts","../../src/config/version.ts","../../src/formatter/gherkin-index.ts","../../src/formatter/hook-index.ts","../../src/formatter/run-hook-tracker.ts","../../src/formatter/suite-builder.ts"],"sourcesContent":["export { default } from './formatter.js';\n","import { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { Formatter, type IFormatterOptions } from '@cucumber/cucumber';\nimport type { Envelope, GherkinDocument, Pickle, TestCase } from '@cucumber/messages';\n\nimport { resolveConfig, type QualflareCucumberOptions, type ResolvedFormatterConfig } from '../config/resolve-config.js';\nimport { logger } from '../shared/logger.js';\nimport { AttachmentBudget } from './attachment-budget.js';\nimport { AttemptTracker } from './attempt-tracker.js';\nimport { buildCase, type FinishedCase } from './case-builder.js';\nimport { buildCollectPayload } from './collect-builder.js';\nimport { GherkinIndex } from './gherkin-index.js';\nimport { buildHookIndex, type HookIndex } from './hook-index.js';\nimport { RunHookTracker } from './run-hook-tracker.js';\nimport { groupIntoSuites } from './suite-builder.js';\n\nexport default class QualflareCucumberFormatter extends Formatter {\n private readonly config: ResolvedFormatterConfig;\n private readonly gherkin = new GherkinIndex();\n private readonly hookIndex: HookIndex;\n private readonly pickleIndex = new Map<string, Pickle>();\n private readonly testCaseIndex = new Map<string, TestCase>();\n private readonly attachmentBudget: AttachmentBudget;\n private readonly attemptTracker: AttemptTracker;\n private readonly runHookTracker = new RunHookTracker();\n private readonly finishedCases: FinishedCase[] = [];\n /** One promise per `testCaseFinished` envelope, resolving once that\n * scenario's `AttemptTracker.finish()` (which itself awaits any pending\n * video uploads — see its doc comment) has settled and, if it produced a\n * result, been pushed into `finishedCases`. `finished()` awaits all of\n * these before building/uploading the Collect payload, so a scenario\n * whose only attachment is a still-uploading video is never silently\n * dropped from the report. */\n private readonly pendingCaseBuilds: Promise<void>[] = [];\n\n constructor(options: IFormatterOptions) {\n super(options);\n // cucumber-js's `formatOptions` is untyped (`FormatOptions` has only an\n // index signature) — the shape is a contract between the user's config\n // and this formatter, not something cucumber-js itself validates.\n this.config = resolveConfig(options.parsedArgvOptions as QualflareCucumberOptions);\n this.hookIndex = buildHookIndex(options.supportCodeLibrary);\n this.attachmentBudget = new AttachmentBudget(this.config.maxTotalAttachmentBytes);\n this.attemptTracker = new AttemptTracker(\n this.hookIndex,\n this.gherkin,\n this.config,\n this.attachmentBudget,\n );\n\n if (!this.config.enabled) {\n return;\n }\n options.eventBroadcaster.on('envelope', (envelope: Envelope) => this.onEnvelope(envelope));\n }\n\n private onEnvelope(envelope: Envelope): void {\n try {\n this.dispatch(envelope);\n } catch (err) {\n logger.error('failed to process a cucumber-js event:', err);\n }\n }\n\n private dispatch(envelope: Envelope): void {\n if (envelope.gherkinDocument) {\n this.onGherkinDocument(envelope.gherkinDocument);\n return;\n }\n if (envelope.pickle) {\n this.pickleIndex.set(envelope.pickle.id, envelope.pickle);\n return;\n }\n if (envelope.testCase) {\n this.testCaseIndex.set(envelope.testCase.id, envelope.testCase);\n return;\n }\n if (envelope.testCaseStarted) {\n const testCase = this.testCaseIndex.get(envelope.testCaseStarted.testCaseId);\n const pickle = testCase ? this.pickleIndex.get(testCase.pickleId) : undefined;\n if (testCase && pickle) {\n this.attemptTracker.begin(envelope.testCaseStarted, testCase, pickle);\n } else {\n // Should never happen under cucumber-js's documented message\n // ordering (testCase/pickle always precede testCaseStarted) — this\n // scenario attempt would otherwise be silently dropped from the\n // report with no signal at all, so warn rather than swallow it.\n logger.warn(\n `could not resolve testCase/pickle for testCaseStarted \"${envelope.testCaseStarted.id}\" — this scenario attempt will not be uploaded.`,\n );\n }\n return;\n }\n if (envelope.testStepStarted) {\n this.attemptTracker.stepStarted(envelope.testStepStarted);\n return;\n }\n if (envelope.testStepFinished) {\n this.attemptTracker.stepFinished(envelope.testStepFinished);\n return;\n }\n if (envelope.attachment) {\n this.attemptTracker.attachment(envelope.attachment);\n return;\n }\n if (envelope.testCaseFinished) {\n // finish() is async (it awaits any pending video upload for this\n // scenario before its attachments can be read — see its doc comment),\n // but dispatch() itself stays synchronous: cucumber-js's envelope\n // stream doesn't wait for one 'envelope' listener's returned promise\n // before emitting the next, so blocking here would just desync this\n // handler from the events actually arriving. Instead, track the\n // promise and await every one of them in finished(), before the\n // Collect payload is ever built.\n const pending = this.attemptTracker\n .finish(envelope.testCaseFinished)\n .then((finished) => {\n if (!finished) {\n return;\n }\n const pickle = this.pickleIndex.get(finished.pickleId);\n if (pickle) {\n this.finishedCases.push(buildCase(finished.uri, pickle, finished.collapsed, this.gherkin));\n } else {\n logger.warn(`could not resolve pickle \"${finished.pickleId}\" for a finished scenario — it will not be uploaded.`);\n }\n })\n .catch((err) => {\n // Mirrors onEnvelope's own catch — dispatch() itself can no longer\n // catch an error raised inside this deferred chain.\n logger.error('failed to process a cucumber-js event:', err);\n });\n this.pendingCaseBuilds.push(pending);\n return;\n }\n if (envelope.testRunHookStarted) {\n this.runHookTracker.start(envelope.testRunHookStarted);\n return;\n }\n if (envelope.testRunHookFinished) {\n this.runHookTracker.finish(envelope.testRunHookFinished, this.hookIndex);\n return;\n }\n }\n\n private onGherkinDocument(doc: GherkinDocument): void {\n this.gherkin.add(doc);\n }\n\n async finished(): Promise<void> {\n try {\n // Every scenario's Case must be fully built (attachments included,\n // any pending video write settled) before the Collect payload is\n // assembled — see the testCaseFinished dispatch branch above.\n await Promise.all(this.pendingCaseBuilds);\n if (this.config.enabled) {\n this.writeResults();\n }\n } finally {\n await super.finished();\n }\n }\n\n /** Writes this process's Collect payload into `outputDir` under a unique\n * filename. Never uploads: `qualflare-cli collect <outputDir>` does that,\n * merging every file it finds there into one Launch. Multiple shards can\n * therefore share one directory safely — the UUID filename is what keeps\n * them from overwriting each other. */\n private writeResults(): void {\n const suites = groupIntoSuites(this.finishedCases, this.cwd, this.runHookTracker.buildSuite());\n if (suites.length === 0) {\n if (this.config.debug) {\n logger.debug('no scenarios reported — skipping file write.');\n }\n return;\n }\n const payload = buildCollectPayload(suites, this.config);\n if (this.config.shardIndex !== undefined) {\n for (const suite of payload.suites) {\n for (const c of suite.cases) {\n c.shardIndex = this.config.shardIndex;\n }\n }\n }\n\n fs.mkdirSync(this.config.outputDir, { recursive: true });\n const outputPath = path.join(this.config.outputDir, `${randomUUID()}.json`);\n fs.writeFileSync(outputPath, JSON.stringify(payload));\n logger.info(\n `wrote Collect payload to ${outputPath} — run \\`qualflare-cli collect ${this.config.outputDir}\\` to upload it.`,\n );\n }\n}\n","/**\n * Shared constants used across the formatter and the author-facing runtime\n * API.\n */\n\n/** Reserved `World.attach()` media type used to smuggle structured\n * `qualflare.*()` calls (label/tag/step/etc.) from step-definition and hook\n * code back to the formatter process — the only data channel CucumberJS\n * gives user code back to a running formatter. The formatter's attachment\n * handler recognizes this exact media type and replays the message as a\n * model mutation instead of rendering it as a literal attachment. */\nexport const RESERVED_MESSAGE_MEDIA_TYPE = 'application/vnd.qualflare.message+json';\n\n/** Server-side caps this client should respect defensively (see\n * `api-service/internal/core/domain/launch/launch.go`). */\nexport const MAX_SUITES_PER_LAUNCH = 2000;\nexport const MAX_CASES_PER_SUITE = 5000;\nexport const MAX_STEPS_PER_CASE = 1000;\nexport const MAX_PARAMETERS_PER_STEP = 50;\nexport const MAX_ATTACHMENTS_PER_CASE = 50;\nexport const MAX_LABELS_PER_CASE = 100;\nexport const MAX_LINKS_PER_CASE = 20;\nexport const MAX_TAGS_PER_CASE = 64;\nexport const MAX_TAG_LENGTH = 255;\n\n/** Mirrors `launch.MaxAttachmentUploadFileSize` — the server's hard cap on a\n * single `POST /api/v1/attachments/upload-url` request (video). */\nexport const MAX_VIDEO_UPLOAD_BYTES = 50 * 1024 * 1024;\n\n/** Client-side SOFT cap on steps recorded per scenario attempt — well under\n * the server's 1000-per-case hard cap (`MAX_STEPS_PER_CASE`). Once hit,\n * further steps within that attempt are dropped (with a one-time warning),\n * not queued and truncated later. */\nexport const MAX_STEPS_PER_TEST_ATTEMPT = 300;\n","import * as ciInfo from 'ci-info';\n\n/** Detected CI pipeline metadata, matching the wire contract's `ciProvider`/\n * `ciBuildNumber`/`ciRunUrl`/`ciPrNumber` fields exactly (`src/shared/types.ts`). */\nexport interface CiMetadata {\n ciProvider?: string;\n ciBuildNumber?: string;\n ciRunUrl?: string;\n ciPrNumber?: number;\n}\n\ninterface ProviderExtractor {\n detect: (env: NodeJS.ProcessEnv) => boolean;\n providerName: string;\n buildNumber?: (env: NodeJS.ProcessEnv) => string | undefined;\n runUrl?: (env: NodeJS.ProcessEnv) => string | undefined;\n prNumber?: (env: NodeJS.ProcessEnv) => number | undefined;\n}\n\nfunction parsePositiveInt(raw: string | undefined): number | undefined {\n if (!raw) {\n return undefined;\n }\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) && n >= 1 ? n : undefined;\n}\n\nfunction nonEmpty(raw: string | undefined): string | undefined {\n return raw && raw.length > 0 ? raw : undefined;\n}\n\n/** Explicit per-provider extraction for the fields `ci-info` doesn't\n * standardize (build number / run URL / PR number). Each `detect` reads\n * directly off the passed-in `env` — deliberately NOT delegating to\n * `ci-info`'s own per-vendor booleans (e.g. `ciInfo.GITHUB_ACTIONS`), which\n * are computed once against the real `process.env` at module-import time\n * and can't be re-evaluated against an injected env — see the module-level\n * comment on `detectCi` below. Checked in order; first match wins. */\nconst PROVIDERS: ProviderExtractor[] = [\n {\n detect: (env) => env.GITHUB_ACTIONS === 'true',\n providerName: 'GitHub Actions',\n buildNumber: (env) => nonEmpty(env.GITHUB_RUN_NUMBER),\n runUrl: (env) =>\n env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY && env.GITHUB_RUN_ID\n ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`\n : undefined,\n prNumber: (env) => {\n const match = /^refs\\/pull\\/(\\d+)\\/merge$/.exec(env.GITHUB_REF ?? '');\n return match ? parsePositiveInt(match[1]) : undefined;\n },\n },\n {\n detect: (env) => env.GITLAB_CI === 'true',\n providerName: 'GitLab CI',\n buildNumber: (env) => nonEmpty(env.CI_PIPELINE_IID),\n runUrl: (env) => nonEmpty(env.CI_PIPELINE_URL),\n prNumber: (env) => parsePositiveInt(env.CI_MERGE_REQUEST_IID),\n },\n {\n detect: (env) => env.CIRCLECI === 'true',\n providerName: 'CircleCI',\n buildNumber: (env) => nonEmpty(env.CIRCLE_BUILD_NUM),\n runUrl: (env) => nonEmpty(env.CIRCLE_BUILD_URL),\n prNumber: (env) => parsePositiveInt(env.CIRCLE_PR_NUMBER),\n },\n {\n detect: (env) => env.BUILDKITE === 'true',\n providerName: 'Buildkite',\n buildNumber: (env) => nonEmpty(env.BUILDKITE_BUILD_NUMBER),\n runUrl: (env) => nonEmpty(env.BUILDKITE_BUILD_URL),\n prNumber: (env) => {\n const raw = env.BUILDKITE_PULL_REQUEST;\n if (!raw || raw === 'false') {\n return undefined;\n }\n return parsePositiveInt(raw);\n },\n },\n {\n // Jenkins has no simple `JENKINS=true`-style flag; JENKINS_URL is always\n // set by the Jenkins agent and is the conventional detection signal.\n detect: (env) => Boolean(env.JENKINS_URL),\n providerName: 'Jenkins',\n buildNumber: (env) => nonEmpty(env.BUILD_NUMBER),\n runUrl: (env) => nonEmpty(env.BUILD_URL),\n // Jenkins has no standardized PR-number env var across its many PR\n // plugins (Multibranch, GitHub Branch Source, etc.) — deliberately\n // omitted rather than guessing at a plugin-specific variable.\n },\n {\n detect: (env) => env.TF_BUILD === 'True' || env.TF_BUILD === 'true',\n providerName: 'Azure Pipelines',\n buildNumber: (env) => nonEmpty(env.BUILD_BUILDID),\n runUrl: (env) => {\n const collectionUri = env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI;\n const project = env.SYSTEM_TEAMPROJECT;\n const buildId = env.BUILD_BUILDID;\n if (!collectionUri || !project || !buildId) {\n return undefined;\n }\n return `${collectionUri.replace(/\\/+$/, '')}/${encodeURIComponent(project)}/_build/results?buildId=${buildId}`;\n },\n prNumber: (env) => parsePositiveInt(env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER),\n },\n {\n detect: (env) => Boolean(env.BITBUCKET_BUILD_NUMBER),\n providerName: 'Bitbucket Pipelines',\n buildNumber: (env) => nonEmpty(env.BITBUCKET_BUILD_NUMBER),\n runUrl: (env) => {\n const origin = env.BITBUCKET_GIT_HTTP_ORIGIN;\n if (!origin) {\n return undefined;\n }\n const resultsId = env.BITBUCKET_PIPELINE_UUID ?? env.BITBUCKET_BUILD_NUMBER;\n return resultsId ? `${origin}/addon/pipelines/home#!/results/${resultsId}` : undefined;\n },\n prNumber: (env) => parsePositiveInt(env.BITBUCKET_PR_ID),\n },\n];\n\n/**\n * Detects CI pipeline metadata for the `Collect.ciProvider`/`ciBuildNumber`/\n * `ciRunUrl`/`ciPrNumber` fields.\n *\n * IMPORTANT, non-obvious limitation: the `env` parameter only governs the\n * `PROVIDERS` table above (this module's own, directly-env-reading logic).\n * The `ci-info` fallback below does NOT honor it — `ci-info`'s package source\n * computes `exports.name`/`exports.isCI` exactly once, against the REAL\n * `process.env`, at module-import time (`const env = process.env` at its top\n * level) — there is no API to re-evaluate it against a different env object.\n * This is a non-issue in production (this function is always called against\n * the real `process.env` there); `ci-detect.test.ts` exercises the `ci-info`\n * fallback path via `vi.stubEnv` + `vi.resetModules()` (forcing a fresh\n * `ci-info` evaluation) rather than via this function's `env` parameter.\n */\nexport function detectCi(env: NodeJS.ProcessEnv = process.env): CiMetadata {\n const provider = PROVIDERS.find((p) => p.detect(env));\n if (provider) {\n const result: CiMetadata = { ciProvider: provider.providerName };\n const buildNumber = provider.buildNumber?.(env);\n if (buildNumber !== undefined) result.ciBuildNumber = buildNumber;\n const runUrl = provider.runUrl?.(env);\n if (runUrl !== undefined) result.ciRunUrl = runUrl;\n const prNumber = provider.prNumber?.(env);\n if (prNumber !== undefined) result.ciPrNumber = prNumber;\n return result;\n }\n\n // Fallback: ci-info's ~70-provider detection gives us a free-text provider\n // name (the server's `ciProvider` field has no enum — an unrecognized\n // value is always accepted, per its doc comment in shared/types.ts) even\n // for providers our explicit table above doesn't cover a full\n // build-number/run-URL/PR-number extraction for.\n if (ciInfo.name) {\n return { ciProvider: ciInfo.name };\n }\n return {};\n}\n","import { execFileSync } from 'node:child_process';\n\n/** Auto-detected branch/commit — the bottom tier of `resolve-config.ts`'s\n * precedence chain (option > QUALFLARE_BRANCH/QF_BRANCH env > CI env vars >\n * local git subprocess > null). Does NOT include the QUALFLARE_BRANCH/\n * QF_BRANCH-style env aliases — those are resolved at a higher tier,\n * directly in `resolve-config.ts`, before this module is ever consulted. */\nexport interface GitInfo {\n branch?: string;\n commit?: string;\n}\n\n/** Injectable so tests never actually shell out. `stdio: ['ignore', 'pipe',\n * 'ignore']` suppresses git's own stderr chatter (e.g. \"fatal: not a git\n * repository\") from leaking into the CI log for what is, from this plugin's\n * perspective, an entirely expected, non-fatal outcome. */\nexport type ExecGit = (args: string[], cwd: string) => string;\n\nconst defaultExecGit: ExecGit = (args, cwd) =>\n execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });\n\nfunction firstEnv(env: NodeJS.ProcessEnv, ...names: string[]): string | undefined {\n for (const name of names) {\n const value = env[name];\n if (value) {\n return value;\n }\n }\n return undefined;\n}\n\nfunction detectBranchFromGit(exec: ExecGit, cwd: string): string | undefined {\n try {\n // Empty on detached HEAD (the `-q` flag suppresses the error and the\n // command still exits 0 with no output in that case on some git\n // versions, hence the explicit empty-string check as well as the\n // try/catch for versions that exit non-zero instead).\n const out = exec(['symbolic-ref', '--short', '-q', 'HEAD'], cwd).trim();\n return out || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction detectCommitFromGit(exec: ExecGit, cwd: string): string | undefined {\n try {\n const out = exec(['rev-parse', 'HEAD'], cwd).trim();\n return out || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Auto-detects branch/commit: CI-provider env vars first (cheap, no\n * subprocess), falling back to a local `git` subprocess only for whichever\n * of branch/commit the env vars didn't resolve — mirroring\n * `qualflare-cli/internal/config/config.go`'s `LoadFromEnv`\n * (`getFirstEnv(\"GIT_BRANCH\", \"GITHUB_REF_NAME\", \"CI_COMMIT_REF_NAME\",\n * \"BITBUCKET_BRANCH\")` / the equivalent commit chain) and its `DetectGit`'s\n * \"only shell out for what's actually missing\" behavior (BUG-39 in that\n * file: forking `git` on every CLI invocation, even `--help`, was wasteful —\n * the same reasoning applies here, this reporter should not fork two `git`\n * processes on every `cucumber-js` run when CI env vars already cover both\n * values, or when the caller already resolved both from options/env at a\n * higher precedence tier and doesn't need this module at all).\n */\nexport function detectGit(\n env: NodeJS.ProcessEnv = process.env,\n cwd: string = process.cwd(),\n exec: ExecGit = defaultExecGit,\n): GitInfo {\n const branch =\n firstEnv(env, 'GIT_BRANCH', 'GITHUB_REF_NAME', 'CI_COMMIT_REF_NAME', 'BITBUCKET_BRANCH') ??\n detectBranchFromGit(exec, cwd);\n const commit =\n firstEnv(env, 'GIT_COMMIT', 'GITHUB_SHA', 'CI_COMMIT_SHA', 'BITBUCKET_COMMIT') ??\n detectCommitFromGit(exec, cwd);\n\n const result: GitInfo = {};\n if (branch !== undefined) result.branch = branch;\n if (commit !== undefined) result.commit = commit;\n return result;\n}\n","import { MAX_VIDEO_UPLOAD_BYTES } from '../shared/constants.js';\nimport type { Platform } from '../shared/types.js';\nimport { detectCi, type CiMetadata } from './ci-detect.js';\nimport { detectGit, type GitInfo } from './git-detect.js';\n\n/** Options passed via the `--format-options`/`formatOptions` value the user\n * configures for `@qualflare/cucumberjs/formatter` (e.g. in `cucumber.js` /\n * `cucumber.json`). Every field here also has an environment-variable\n * override — see the precedence table in the README / plan. */\nexport interface QualflareCucumberOptions {\n environment?: string;\n language?: string;\n milestone?: number | null;\n branch?: string | null;\n commit?: string | null;\n platform?: Platform;\n framework?: string;\n os?: string;\n browser?: string;\n properties?: Record<string, string>;\n /** Max 64 chars. Free text, no enum — an unrecognized CI provider must\n * never be rejected. Auto-detected via `ci-detect.ts` when omitted. */\n ciProvider?: string;\n ciBuildNumber?: string;\n ciRunUrl?: string;\n ciPrNumber?: number;\n attachScreenshots?: boolean;\n /** Include `BeforeStep`/`AfterStep` hook executions as synthetic steps.\n * Off by default — these run once per Gherkin step and can multiply the\n * step count several-fold for suites with global per-step instrumentation\n * hooks (e.g. a screenshot-after-every-step hook), which is noisy as a\n * default but valuable as an explicit opt-in. */\n includeStepHooks?: boolean;\n maxAttachmentBytes?: number;\n maxTotalAttachmentBytes?: number;\n /** Per-video byte cap, checked before the file is written. Default 50MB,\n * matching the server's own hard cap. */\n maxVideoBytes?: number;\n debug?: boolean;\n /** `false` fully disables accumulation/upload (a complete no-op) but the\n * formatter still no-ops cleanly rather than throwing. */\n enabled?: boolean;\n /** Directory `finished()` writes this process's report file (and any\n * video attachments) into. Default `./qualflare-results`. Always active —\n * this formatter never uploads anything itself; `qualflare-cli` reads\n * whatever ends up in this directory. Every JSON file this process writes\n * is uniquely named, so multiple shards can safely share one `outputDir`\n * without colliding — see docs/LIMITATIONS.md. */\n outputDir?: string;\n /** This process's 0-based position among parallel shards of the same CI\n * run, stamped onto every case it reports. Purely a label: `qualflare-cli`\n * merges by \"every file in the directory\", not by this value, so an\n * unset shardIndex costs attribution, never correctness.\n *\n * Auto-detected, in order: `QUALFLARE_SHARD_INDEX`, then a best-effort\n * scan of `process.argv` for cucumber-js's own `--shard INDEX/TOTAL`\n * (whose index is 1-based, so it is converted). cucumber-js routes that\n * flag to `configuration.sources.shard`, and a formatter is only ever\n * handed `configuration.options` — so argv is the only place a formatter\n * can observe it, and only when it was passed on the command line rather\n * than via a config file. */\n shardIndex?: number;\n}\n\nexport interface ResolvedFormatterConfig {\n environment: string;\n language: string;\n milestone: number | null;\n branch: string | null;\n commit: string | null;\n platform: Platform;\n framework: string;\n os?: string;\n browser?: string;\n properties?: Record<string, string>;\n ciProvider?: string;\n ciBuildNumber?: string;\n ciRunUrl?: string;\n ciPrNumber?: number;\n attachScreenshots: boolean;\n includeStepHooks: boolean;\n maxAttachmentBytes: number;\n maxTotalAttachmentBytes: number;\n maxVideoBytes: number;\n debug: boolean;\n enabled: boolean;\n outputDir: string;\n shardIndex?: number;\n}\n\nfunction firstEnv(...names: string[]): string | undefined {\n for (const name of names) {\n const value = process.env[name];\n if (value !== undefined && value !== '') {\n return value;\n }\n }\n return undefined;\n}\n\nfunction envBool(...names: string[]): boolean | undefined {\n const raw = firstEnv(...names);\n if (raw === undefined) {\n return undefined;\n }\n return raw === 'true' || raw === '1';\n}\n\nfunction envInt(...names: string[]): number | undefined {\n const raw = firstEnv(...names);\n if (raw === undefined) {\n return undefined;\n }\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\n/** Best-effort read of cucumber-js's own `--shard INDEX/TOTAL` flag from\n * `process.argv`, returned 0-based.\n *\n * cucumber-js does parse this flag, but routes it to\n * `configuration.sources.shard`, while a formatter is only ever handed\n * `configuration.options` (see `api/formatters.js`) — so there is no\n * supported API for a formatter to read it. argv is the one place it is\n * observable, and only when the user passed it on the command line rather\n * than via a `cucumber.js` config file; that is why this sits BELOW\n * `QUALFLARE_SHARD_INDEX` in precedence rather than replacing it.\n *\n * cucumber documents the flag's index as 1-based (\"The index starts at 1\")\n * and normalizes it internally with `parseInt(idx) - 1`; we match that, so\n * `--shard 1/3` is shard 0. A malformed value yields `undefined` rather\n * than a wrong shard label — cucumber validates the same `<n>/<n>` shape\n * and would already have rejected it. */\nfunction argvShardIndex(argv: readonly string[] = process.argv): number | undefined {\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === undefined) {\n continue;\n }\n const raw = arg === '--shard' ? argv[i + 1] : arg.startsWith('--shard=') ? arg.slice('--shard='.length) : undefined;\n if (raw === undefined) {\n continue;\n }\n if (!/^\\d+\\/\\d+$/.test(raw)) {\n return undefined;\n }\n const oneBased = Number.parseInt(raw.split('/')[0] ?? '', 10);\n return Number.isFinite(oneBased) && oneBased >= 1 ? oneBased - 1 : undefined;\n }\n return undefined;\n}\n\n/** Resolves the full formatter configuration from, in order: the explicit\n * `options` (the formatter's own `formatOptions`), then `QUALFLARE_*`\n * environment variables, then `QF_*` (compat alias with the existing Go\n * CLI, where an equivalent exists), then a hardcoded default.\n *\n * Branch/commit precedence: `options.branch`/`.commit` (including an\n * explicit `null`, which is respected as \"no auto-detection wanted\" rather\n * than triggering the fallback tiers below it) > `QUALFLARE_BRANCH`/\n * `QF_BRANCH` env (and the commit equivalent) > CI-provider env vars > a\n * local `git` subprocess (`git-detect.ts`) > `null`. The subprocess tier is\n * skipped entirely — no `git` process is forked — once both branch and\n * commit are already resolved from options/env, mirroring\n * `qualflare-cli/internal/config/config.go`'s `DetectGit`'s early return.\n *\n * CI-metadata precedence (`ciProvider`/`ciBuildNumber`/`ciRunUrl`/\n * `ciPrNumber`): the corresponding `options.ci*` field, else `ci-detect.ts`'s\n * auto-detection (per-provider extraction table, falling back to the\n * `ci-info` package's ~70-provider free-text name).\n *\n * `deps` lets tests inject fake `detectGit`/`detectCi` implementations\n * instead of the real ones (which shell out to `git` and read the real\n * `process.env`/`ci-info` module state) — defaults to the real detectors,\n * so every production call site (the formatter's constructor calls\n * `resolveConfig(options)` with no second argument) is unaffected.\n */\nexport function resolveConfig(\n options: QualflareCucumberOptions,\n deps: { detectGit?: () => GitInfo; detectCi?: () => CiMetadata } = {},\n): ResolvedFormatterConfig {\n const doDetectGit = deps.detectGit ?? detectGit;\n const doDetectCi = deps.detectCi ?? detectCi;\n\n const enabled = options.enabled ?? envBool('QUALFLARE_ENABLED') ?? true;\n // `||`, not `??` — matching `environment`/`language` below: an explicit\n const outputDir = options.outputDir || firstEnv('QUALFLARE_OUTPUT_DIR') || './qualflare-results';\n const shardIndex = options.shardIndex ?? envInt('QUALFLARE_SHARD_INDEX') ?? argvShardIndex();\n\n const milestoneRaw = options.milestone !== undefined ? options.milestone : envInt('QUALFLARE_MILESTONE', 'QF_MILESTONE');\n const milestone = milestoneRaw !== undefined && milestoneRaw !== null && milestoneRaw >= 1 ? milestoneRaw : null;\n\n const envBranch = firstEnv('QUALFLARE_BRANCH', 'QF_BRANCH');\n const envCommit = firstEnv('QUALFLARE_COMMIT', 'QF_COMMIT');\n const needsGitDetection =\n (options.branch === undefined && envBranch === undefined) ||\n (options.commit === undefined && envCommit === undefined);\n const detectedGit = needsGitDetection ? doDetectGit() : {};\n\n const branch = options.branch !== undefined ? options.branch : (envBranch ?? detectedGit.branch ?? null);\n const commit = options.commit !== undefined ? options.commit : (envCommit ?? detectedGit.commit ?? null);\n\n const detectedCi = doDetectCi();\n const ciProvider = options.ciProvider ?? detectedCi.ciProvider;\n const ciBuildNumber = options.ciBuildNumber ?? detectedCi.ciBuildNumber;\n const ciRunUrl = options.ciRunUrl ?? detectedCi.ciRunUrl;\n const ciPrNumber = options.ciPrNumber ?? detectedCi.ciPrNumber;\n\n return {\n // `||` (truthy check), not `??`, for these three REQUIRED-non-empty wire\n // fields — an explicit `''` option must not silently win over the\n // default (the server rejects an empty `environment`). Ported verbatim from\n // qualflare-cypress, where this was found via deep adversarial review.\n environment: (options.environment || undefined) ?? firstEnv('QUALFLARE_ENVIRONMENT', 'QF_ENVIRONMENT') ?? 'development',\n language: (options.language || undefined) ?? firstEnv('QUALFLARE_LANGUAGE', 'QF_LANGUAGE') ?? 'en-US',\n milestone,\n branch,\n commit,\n platform: options.platform ?? 'web',\n framework: options.framework || 'cucumber',\n os: options.os,\n browser: options.browser,\n properties: options.properties,\n ciProvider,\n ciBuildNumber,\n ciRunUrl,\n ciPrNumber,\n attachScreenshots: options.attachScreenshots ?? envBool('QUALFLARE_ATTACH_SCREENSHOTS') ?? true,\n includeStepHooks: options.includeStepHooks ?? envBool('QUALFLARE_INCLUDE_STEP_HOOKS') ?? false,\n maxAttachmentBytes: options.maxAttachmentBytes ?? envInt('QUALFLARE_MAX_ATTACHMENT_BYTES') ?? 1_500_000,\n maxTotalAttachmentBytes:\n options.maxTotalAttachmentBytes ?? envInt('QUALFLARE_MAX_TOTAL_ATTACHMENT_BYTES') ?? 750_000,\n maxVideoBytes: options.maxVideoBytes ?? envInt('QUALFLARE_MAX_VIDEO_BYTES') ?? MAX_VIDEO_UPLOAD_BYTES,\n debug: options.debug ?? envBool('QUALFLARE_DEBUG', 'QF_DEBUG') ?? false,\n enabled,\n outputDir,\n shardIndex,\n };\n}\n","/**\n * A minimal logger writing to stderr. Deliberately avoids stdout, since\n * that's typically `cucumber-js`'s own test-output stream and shouldn't be\n * polluted with reporter diagnostics.\n */\n\nconst PREFIX = '[qualflare-cucumberjs]';\n\nexport const logger = {\n debug(...args: unknown[]): void {\n console.debug(PREFIX, ...args);\n },\n info(...args: unknown[]): void {\n console.log(PREFIX, ...args);\n },\n warn(...args: unknown[]): void {\n console.warn(PREFIX, ...args);\n },\n error(...args: unknown[]): void {\n console.error(PREFIX, ...args);\n },\n};\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { logger } from '../shared/logger.js';\nimport type { Attachment } from '../shared/types.js';\nimport { writeVideoAttachment } from './video-uploader.js';\n\n/** Extensions/mime-prefixes routed through the video-upload flow\n * (`resolveVideoAttachment`) instead of the inline-base64 path below.\n * Broader than the server's own MIME allowlist (`.avi`/`.mkv` included) so\n * this still correctly IDENTIFIES a video attachment even in a format the\n * server can't accept — `resolveVideoAttachment`/`resolveVideoMimeType` is\n * what actually enforces the narrower allowlist and warns/skips a format\n * outside it. */\nconst VIDEO_EXTENSIONS = new Set(['.mp4', '.webm', '.mov', '.avi', '.mkv']);\n\nexport interface AttachmentBudgetConfig {\n attachScreenshots: boolean;\n maxAttachmentBytes: number;\n maxTotalAttachmentBytes: number;\n maxVideoBytes: number;\n outputDir: string;\n}\n\n/** One `World.attach()` call (real user attachment, or `qualflare.attachment\n * ()`/`attachmentFromFile()`), not yet resolved into a wire `Attachment`.\n * Exactly one of `content`/`path` is set — `content` for cucumber-js's\n * native in-memory delivery (a Buffer/base64 string, the common case for\n * real `World.attach()` calls) or `qualflare.attachment()`; `path` only for\n * `qualflare.attachmentFromFile()`, cucumber-js itself never delivers a bare\n * file path. */\nexport interface PendingAttachment {\n name: string;\n mimeType?: string;\n stepIndex?: number;\n /** Base64-encoded. */\n content?: string;\n path?: string;\n}\n\n/**\n * Tracks cumulative attached bytes across the whole `cucumber-js` process\n * (one instance per formatter, reused across every attachment resolved),\n * so the final POST doesn't silently exceed the request body limit. Ported\n * verbatim in spirit from `@qualflare/cypress`'s `AttachmentBudget`.\n */\nexport class AttachmentBudget {\n private used = 0;\n\n constructor(private readonly maxTotalBytes: number) {}\n\n /** Atomically checks-and-reserves `bytes` against the remaining budget.\n * Returns false (reserving nothing) if it would exceed the total. */\n tryReserve(bytes: number): boolean {\n if (this.used + bytes > this.maxTotalBytes) {\n return false;\n }\n this.used += bytes;\n return true;\n }\n\n get usedBytes(): number {\n return this.used;\n }\n}\n\ntype ReadResult = { skipped: false; content: string } | { skipped: true; reason: string };\n\nexport function isVideoLike(mimeType: string | undefined, filePath: string | undefined): boolean {\n if (mimeType?.toLowerCase().startsWith('video/')) {\n return true;\n }\n if (filePath && VIDEO_EXTENSIONS.has(path.extname(filePath).toLowerCase())) {\n return true;\n }\n return false;\n}\n\nfunction readAttachmentFile(filePath: string, maxAttachmentBytes: number, budget: AttachmentBudget): ReadResult {\n let size: number;\n try {\n // Stat BEFORE reading — an oversized file must never be loaded into\n // memory just to discover it should be skipped.\n size = fs.statSync(filePath).size;\n } catch (err) {\n return { skipped: true, reason: `could not stat file: ${(err as Error).message}` };\n }\n if (size > maxAttachmentBytes) {\n return {\n skipped: true,\n reason: `${size} bytes exceeds the configured per-attachment cap of ${maxAttachmentBytes} bytes`,\n };\n }\n if (!budget.tryReserve(size)) {\n return {\n skipped: true,\n reason: `would exceed this run's total attachment budget (${budget.usedBytes} bytes already used)`,\n };\n }\n try {\n const content = fs.readFileSync(filePath).toString('base64');\n return { skipped: false, content };\n } catch (err) {\n return { skipped: true, reason: `could not read file: ${(err as Error).message}` };\n }\n}\n\n/**\n * Resolves one NON-video pending attachment into a wire `Attachment`, or\n * `undefined` if it should be skipped entirely (per the plan's resolved\n * decision — an oversized/over-budget attachment is dropped, not degraded\n * to a contentless stub, since the server's `path` field is explicitly\n * informational/never-fetched). Unlike `@qualflare/cypress`'s\n * `resolveAttachments()` (which batch-resolves a Case's whole array at\n * case-finish time), this resolves one attachment at a time as its\n * `attachment` envelope arrives — matching cucumber-js's per-envelope\n * event stream.\n *\n * Callers MUST check `isVideoLike()` first and route a video-like pending\n * attachment to `resolveVideoAttachment()` instead — this function assumes\n * it is not one (see `attempt-tracker.ts`'s call sites).\n */\nexport function resolvePendingAttachment(\n pending: PendingAttachment,\n config: AttachmentBudgetConfig,\n budget: AttachmentBudget,\n): Attachment | undefined {\n if (!config.attachScreenshots) {\n return undefined;\n }\n if (pending.content !== undefined) {\n const bytes = Buffer.byteLength(pending.content, 'base64');\n if (bytes > config.maxAttachmentBytes) {\n logger.warn(\n `skipping attachment \"${pending.name}\": ${bytes} bytes exceeds the configured per-attachment cap of ${config.maxAttachmentBytes} bytes`,\n );\n return undefined;\n }\n if (!budget.tryReserve(bytes)) {\n logger.warn(\n `skipping attachment \"${pending.name}\": would exceed this run's total attachment budget (${budget.usedBytes} bytes already used)`,\n );\n return undefined;\n }\n return { name: pending.name, mimeType: pending.mimeType, content: pending.content, stepIndex: pending.stepIndex };\n }\n if (pending.path) {\n const result = readAttachmentFile(pending.path, config.maxAttachmentBytes, budget);\n if (result.skipped) {\n logger.warn(`skipping attachment \"${pending.name}\" (${pending.path}): ${result.reason}`);\n return undefined;\n }\n return {\n name: pending.name,\n mimeType: pending.mimeType,\n content: result.content,\n path: pending.path,\n stepIndex: pending.stepIndex,\n };\n }\n return undefined;\n}\n\n/**\n * Resolves one video-like pending attachment (`isVideoLike()` already true)\n * into a wire `Attachment` carrying `storageKey`/`fileSize` instead of\n * `content`, via the presigned-upload-URL flow — or `undefined` if it should\n * be skipped (uploads disabled, unsupported format, oversized, or a\n * network/API error; each case logs why). Async, unlike\n * `resolvePendingAttachment` — see `attempt-tracker.ts`'s\n * `pendingVideoWrites` for how callers reconcile that with cucumber-js's\n * synchronous, per-envelope event stream.\n */\nexport async function resolveVideoAttachment(\n pending: PendingAttachment,\n config: AttachmentBudgetConfig,\n): Promise<Attachment | undefined> {\n if (!config.attachScreenshots) {\n return undefined;\n }\n const written = writeVideoAttachment(pending, config.outputDir, config.maxVideoBytes);\n if (!written) {\n // writeVideoAttachment already logged why.\n return undefined;\n }\n return {\n name: pending.name,\n mimeType: written.mimeType,\n localVideoPath: written.localVideoPath,\n fileSize: written.fileSize,\n stepIndex: pending.stepIndex,\n };\n}\n","import { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { logger } from '../shared/logger.js';\n\n\n/** Extension <-> MIME type for the video formats the server accepts (see\n * `launch.AllowedAttachmentUploadMimeTypes` server-side). */\nconst VIDEO_MIME_TYPES_BY_EXTENSION: Record<string, string> = {\n '.mp4': 'video/mp4',\n '.webm': 'video/webm',\n '.mov': 'video/quicktime',\n};\nconst EXTENSION_BY_VIDEO_MIME_TYPE: Record<string, string> = {\n 'video/mp4': '.mp4',\n 'video/webm': '.webm',\n 'video/quicktime': '.mov',\n};\n\nexport interface ResolvedVideoMimeType {\n mimeType: string;\n extension: string;\n}\n\n/**\n * Determines the server-accepted `{mimeType, extension}` pair for a pending\n * video attachment, or `undefined` if neither `filePath`'s extension nor\n * `mimeType` maps to one of the three formats the server allows.\n * `filePath` (a real local file, from `qualflare.attachmentFromFile()`)\n * takes priority when present — its extension is authoritative and cannot\n * disagree with itself the way a caller-supplied `mimeType` claim could.\n * Without a `filePath` (in-memory `World.attach()`/`qualflare.attachment()`\n * content), `mimeType` is the only signal available.\n */\nexport function resolveVideoMimeType(mimeType: string | undefined, filePath: string | undefined): ResolvedVideoMimeType | undefined {\n if (filePath) {\n const extension = path.extname(filePath).toLowerCase();\n const resolvedMimeType = VIDEO_MIME_TYPES_BY_EXTENSION[extension];\n return resolvedMimeType ? { mimeType: resolvedMimeType, extension } : undefined;\n }\n const normalized = mimeType?.toLowerCase();\n const extension = normalized ? EXTENSION_BY_VIDEO_MIME_TYPE[normalized] : undefined;\n return normalized && extension ? { mimeType: normalized, extension } : undefined;\n}\n\nexport interface VideoWriteResult {\n /** Filename relative to the `outputDir` this was written into. */\n localVideoPath: string;\n fileSize: number;\n mimeType: string;\n}\n\n/**\n * Writes one pending video attachment's bytes into `outputDir` under a\n * unique filename — copying (`fs.copyFileSync`) when it names a real local\n * file, or decoding+writing when it's in-memory base64 content (the\n * `World.attach()`/`qualflare.attachment()` path, which has no file to\n * copy). Unlike qualflare-cypress, where a video is always a file Cypress\n * recorded, this formatter has to handle both — cucumber-js has no\n * \"one recorded file per run\" concept.\n *\n * `qualflare-cli` uploads whatever lands here later, once it has a real\n * auth token; this process never makes a network call.\n *\n * Best-effort: any failure (unsupported format, oversized, unreadable\n * source, write failure) is logged as a warning and returns `undefined`\n * rather than throwing — a video is never worth failing a test run over.\n */\nexport function writeVideoAttachment(\n pending: { name: string; mimeType?: string; path?: string; content?: string },\n outputDir: string,\n maxVideoBytes: number,\n): VideoWriteResult | undefined {\n const resolved = resolveVideoMimeType(pending.mimeType, pending.path);\n if (!resolved) {\n logger.warn(`skipping video attachment \"${pending.name}\": unsupported video format.`);\n return undefined;\n }\n\n const localVideoPath = `${randomUUID()}${resolved.extension}`;\n const destination = path.join(outputDir, localVideoPath);\n\n if (pending.path !== undefined) {\n let fileSize: number;\n try {\n fileSize = fs.statSync(pending.path).size;\n } catch (err) {\n logger.warn(`skipping video attachment \"${pending.path}\": could not stat file: ${(err as Error).message}`);\n return undefined;\n }\n if (fileSize > maxVideoBytes) {\n logger.warn(\n `skipping video attachment \"${pending.path}\": ${fileSize} bytes exceeds the configured maxVideoBytes cap of ${maxVideoBytes} bytes.`,\n );\n return undefined;\n }\n try {\n fs.mkdirSync(outputDir, { recursive: true });\n fs.copyFileSync(pending.path, destination);\n } catch (err) {\n logger.warn(`skipping video attachment \"${pending.path}\": could not copy file: ${(err as Error).message}`);\n return undefined;\n }\n return { localVideoPath, fileSize, mimeType: resolved.mimeType };\n }\n\n if (pending.content !== undefined) {\n const fileSize = Buffer.byteLength(pending.content, 'base64');\n if (fileSize > maxVideoBytes) {\n logger.warn(\n `skipping video attachment \"${pending.name}\": ${fileSize} bytes exceeds the configured maxVideoBytes cap of ${maxVideoBytes} bytes.`,\n );\n return undefined;\n }\n try {\n fs.mkdirSync(outputDir, { recursive: true });\n fs.writeFileSync(destination, Buffer.from(pending.content, 'base64'));\n } catch (err) {\n logger.warn(`skipping video attachment \"${pending.name}\": could not write file: ${(err as Error).message}`);\n return undefined;\n }\n return { localVideoPath, fileSize, mimeType: resolved.mimeType };\n }\n\n return undefined;\n}\n","import {\n getWorstTestStepResult,\n type Attachment as MessageAttachment,\n type Pickle,\n type TestCase,\n type TestCaseFinished,\n type TestCaseStarted,\n type TestStepFinished,\n type TestStepResult,\n type TestStepStarted,\n} from '@cucumber/messages';\n\nimport { RESERVED_MESSAGE_MEDIA_TYPE, MAX_STEPS_PER_TEST_ATTEMPT } from '../shared/constants.js';\nimport { messageDurationToNs } from '../shared/duration.js';\nimport { logger } from '../shared/logger.js';\nimport type { ManualStepRecord, RuntimeMessage } from '../runtime/message-types.js';\nimport type { Attachment, CasePriority, Label, Link, Step } from '../shared/types.js';\nimport {\n AttachmentBudget,\n isVideoLike,\n resolvePendingAttachment,\n resolveVideoAttachment,\n type AttachmentBudgetConfig,\n type PendingAttachment,\n} from './attachment-budget.js';\nimport { collapseAttempts, type AttemptSnapshot, type CollapsedResult } from './case-builder.js';\nimport type { GherkinIndex } from './gherkin-index.js';\nimport type { HookIndex } from './hook-index.js';\nimport { mapHookStep, mapPickleStep, mapStatus } from './step-mapper.js';\n\nexport interface FinishedAttempts {\n uri: string;\n pickleId: string;\n collapsed: CollapsedResult;\n}\n\ninterface AttemptRecord {\n testCase: TestCase;\n pickle: Pickle;\n attempt: number;\n startedAtMs: number;\n steps: Step[];\n stepResults: TestStepResult[];\n /** Maps a `TestStep.id` to its index in `steps`, once finished — used to\n * correlate a same-step attachment's `stepIndex` on the wire. */\n stepIndexByTestStepId: Map<string, number>;\n currentTestStepId?: string;\n manualSteps: ManualStepRecord[];\n manualStepStack: number[];\n labels: Label[];\n links: Link[];\n tags: string[];\n description?: string;\n priority?: CasePriority;\n properties: Record<string, string>;\n attachments: Attachment[];\n stepCapWarned: boolean;\n /** Not-yet-settled video writes (see `resolveVideoAttachment`) started\n * during this attempt. Each one, once settled, pushes its resulting\n * `Attachment` onto `attachments` above (mutating the array in place —\n * never reassigning it — so a reference already captured elsewhere still\n * sees the push; see `finish()`'s doc comment for why this matters).\n * `finish()` awaits every attempt's own queue before collapsing, so\n * `attachments` is always complete by the time it's read.\n *\n * Still genuinely needed even though writing a video is now synchronous\n * filesystem work rather than an upload: `resolveVideoAttachment` remains\n * an `async` function, so the `.then()` that performs the push runs on a\n * microtask, not inline. Dropping this queue would reintroduce exactly the\n * bug `finish()` documents. */\n pendingVideoWrites: Promise<void>[];\n}\n\n/**\n * The retry-safe core. Two lookup layers, both required because\n * cucumber-js's *grouping* key (a logical scenario, stable across retries —\n * `testCase.id`) and its *live-event-addressing* key (one specific attempt\n * — `testCaseStarted.id`) are genuinely different fields on the wire.\n * `testCaseFinished.willBeRetried === false` is the sole authoritative\n * \"this scenario is really done\" signal — no hash-based grouping anywhere\n * (the deliberate fix for `allure-framework/allure-js#625`/`#1502`, both\n * real bugs caused by Allure's content-hash retry/outline-row grouping).\n */\nexport class AttemptTracker {\n private readonly byTestCaseId = new Map<string, AttemptRecord[]>();\n private readonly byTestCaseStartedId = new Map<string, AttemptRecord>();\n\n constructor(\n private readonly hookIndex: HookIndex,\n private readonly gherkin: GherkinIndex,\n private readonly config: { includeStepHooks: boolean } & AttachmentBudgetConfig,\n private readonly attachmentBudget: AttachmentBudget,\n ) {}\n\n begin(e: TestCaseStarted, testCase: TestCase, pickle: Pickle): void {\n const record: AttemptRecord = {\n testCase,\n pickle,\n attempt: e.attempt,\n startedAtMs: timestampMs(e.timestamp),\n steps: [],\n stepResults: [],\n stepIndexByTestStepId: new Map(),\n manualSteps: [],\n manualStepStack: [],\n labels: [],\n links: [],\n tags: [],\n properties: {},\n attachments: [],\n stepCapWarned: false,\n pendingVideoWrites: [],\n };\n this.byTestCaseStartedId.set(e.id, record);\n const attempts = this.byTestCaseId.get(testCase.id) ?? [];\n attempts.push(record);\n this.byTestCaseId.set(testCase.id, attempts);\n }\n\n stepStarted(e: TestStepStarted): void {\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n if (!record) {\n return;\n }\n record.currentTestStepId = e.testStepId;\n }\n\n stepFinished(e: TestStepFinished): void {\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n if (!record) {\n return;\n }\n record.currentTestStepId = undefined;\n record.stepResults.push(e.testStepResult);\n\n const testStep = record.testCase.testSteps.find((s) => s.id === e.testStepId);\n if (!testStep) {\n return;\n }\n\n let step: Step | undefined;\n if (testStep.pickleStepId) {\n const pickleStep = record.pickle.steps.find((s) => s.id === testStep.pickleStepId);\n if (pickleStep) {\n step = mapPickleStep(record.pickle.uri, pickleStep, e.testStepResult, this.gherkin);\n }\n } else if (testStep.hookId) {\n const hook = this.hookIndex.get(testStep.hookId);\n if (hook && (hook.kind === 'before' || hook.kind === 'after')) {\n step = mapHookStep(hook, e.testStepResult);\n } else if (hook && (hook.kind === 'beforeStep' || hook.kind === 'afterStep') && this.config.includeStepHooks) {\n // Deliberately NOT nested under the pickle step it wraps: BeforeStep\n // fires (and is pushed here) BEFORE that step, so \"the most recently\n // pushed step\" would be the PREVIOUS, unrelated step at that point —\n // an earlier version of this nested BeforeStep under the wrong\n // parent. AfterStep could be nested correctly (its wrapped step is\n // already pushed by the time it fires), but nesting one and not the\n // other would be inconsistent, so both stay flat, root-level steps —\n // still informative via their position immediately adjacent to the\n // step they wrap in the flat, chronologically-ordered array.\n step = mapHookStep(hook, e.testStepResult);\n }\n }\n\n if (!step) {\n return;\n }\n if (record.steps.length >= MAX_STEPS_PER_TEST_ATTEMPT) {\n if (!record.stepCapWarned) {\n record.stepCapWarned = true;\n logger.warn(\n `reached the ${MAX_STEPS_PER_TEST_ATTEMPT}-step-per-attempt cap — further steps in this scenario attempt will not be uploaded.`,\n );\n }\n return;\n }\n record.stepIndexByTestStepId.set(e.testStepId, record.steps.length);\n record.steps.push(step);\n }\n\n /** Handles both a real user `World.attach()` call (becomes a wire\n * `Attachment`) and a `qualflare.*()` reserved-media-type message\n * (unwrapped and applied as a model mutation instead). */\n attachment(e: MessageAttachment): void {\n if (!e.testCaseStartedId) {\n // A BeforeAll/AfterAll-scoped attachment (`testRunHookStartedId` set\n // instead) — there is no Case to attach it to; see `run-hook-tracker\n // .ts`'s doc comment for why BeforeAll/AfterAll attachments are out of\n // scope for v1.\n return;\n }\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n if (!record) {\n return;\n }\n\n if (e.mediaType === RESERVED_MESSAGE_MEDIA_TYPE) {\n let message: RuntimeMessage;\n try {\n message = JSON.parse(e.contentEncoding === 'BASE64' ? Buffer.from(e.body, 'base64').toString('utf8') : e.body);\n } catch {\n logger.warn('received a malformed qualflare runtime message — ignoring it.');\n return;\n }\n this.applyRuntimeMessage(record, message, e.testStepId);\n return;\n }\n\n const stepIndex = this.resolveStepIndex(record, e.testStepId);\n const content = e.contentEncoding === 'BASE64' ? e.body : Buffer.from(e.body, 'utf8').toString('base64');\n this.resolveAttachment(record, { name: e.fileName || 'attachment', mimeType: e.mediaType, content, stepIndex });\n }\n\n /** Resolves one pending attachment, routing a video-like one through the\n * write-to-`outputDir` flow (tracked in `record.pendingVideoWrites` so\n * `finish()` can wait for it) and everything else through the synchronous\n * inline path — shared by the real `World.attach()` handler above and both\n * `qualflare.attachment()`/`attachmentFromFile()` runtime-message cases\n * below. */\n private resolveAttachment(record: AttemptRecord, pending: PendingAttachment): void {\n if (isVideoLike(pending.mimeType, pending.path)) {\n const write = resolveVideoAttachment(pending, this.config).then((resolved) => {\n if (resolved) {\n record.attachments.push(resolved);\n }\n });\n record.pendingVideoWrites.push(write);\n return;\n }\n const resolved = resolvePendingAttachment(pending, this.config, this.attachmentBudget);\n if (resolved) {\n record.attachments.push(resolved);\n }\n }\n\n /** Resolves which step index an attachment (real or `qualflare.*()`\n * message) belongs to. `stepIndexByTestStepId` only gets an entry once a\n * step is FINISHED — but `World.attach()` (the only channel available,\n * used by both a real user attachment and every `qualflare.*()` call) is\n * always called from WITHIN a step's body, i.e. strictly BETWEEN that\n * step's `testStepStarted` and `testStepFinished`. So the map lookup\n * alone can never resolve the step that's currently attaching — this was\n * a real bug found in self-review, caught by a unit test that attaches\n * mid-step instead of only after it finishes. While a step is in flight,\n * `record.steps.length` (the array's CURRENT length, before that step has\n * been pushed) is exactly the index it will occupy once it does finish —\n * single-threaded, event-ordered execution guarantees nothing else can be\n * pushed in between. */\n private resolveStepIndex(record: AttemptRecord, testStepId: string | undefined): number | undefined {\n if (testStepId === undefined) {\n return undefined;\n }\n if (record.currentTestStepId === testStepId) {\n return record.steps.length;\n }\n return record.stepIndexByTestStepId.get(testStepId);\n }\n\n private applyRuntimeMessage(record: AttemptRecord, message: RuntimeMessage, testStepId: string | undefined): void {\n switch (message.type) {\n case 'label':\n record.labels.push({ name: message.name, value: message.value });\n return;\n case 'link':\n record.links.push({ type: message.linkType ?? 'custom', name: message.name, url: message.url });\n return;\n case 'tag':\n record.tags.push(...message.tags);\n return;\n case 'description':\n record.description = message.text;\n return;\n case 'priority':\n record.priority = message.value;\n return;\n case 'parameter': {\n const openStepIndex = record.manualStepStack[record.manualStepStack.length - 1];\n if (openStepIndex !== undefined) {\n const step = record.manualSteps[openStepIndex]!;\n step.parameters = step.parameters ?? [];\n step.parameters.push({ name: message.name, value: message.value, masked: message.masked });\n } else {\n record.properties[message.name] = message.value ?? '';\n }\n return;\n }\n case 'attachment': {\n const stepIndex = testStepId !== undefined ? record.stepIndexByTestStepId.get(testStepId) : undefined;\n this.resolveAttachment(record, { name: message.name, mimeType: message.mimeType, content: message.contentBase64, stepIndex });\n return;\n }\n case 'attachment_from_file': {\n const stepIndex = testStepId !== undefined ? record.stepIndexByTestStepId.get(testStepId) : undefined;\n this.resolveAttachment(record, { name: message.name, mimeType: message.mimeType, path: message.path, stepIndex });\n return;\n }\n case 'step_start': {\n const parentIndex = record.manualStepStack.length > 0 ? record.manualStepStack[record.manualStepStack.length - 1] : undefined;\n const step: ManualStepRecord = { name: message.name, status: 'passed', startedAt: message.timestamp, parentIndex };\n record.manualStepStack.push(record.manualSteps.length);\n record.manualSteps.push(step);\n return;\n }\n case 'step_stop': {\n const openIndex = record.manualStepStack.pop();\n if (openIndex === undefined) {\n return;\n }\n const step = record.manualSteps[openIndex]!;\n step.status = message.status;\n step.error = message.error;\n step.durationMs = Math.max(0, message.timestamp - step.startedAt);\n return;\n }\n }\n }\n\n /** Returns the collapsed result once all attempts of this logical\n * scenario have arrived, or `undefined` if more attempts are coming\n * (`willBeRetried === true`).\n *\n * Async because it must first await every attempt's own\n * `pendingVideoWrites` (any video attached anywhere across every retry\n * of this scenario). This is load-bearing, not just tidiness:\n * `collapseAttempts`/`buildCase` read `attachments` by REFERENCE, not by\n * copy, and `buildCase` runs synchronously right after this resolves — a\n * scenario whose ONLY attachment is a still-uploading video would have an\n * EMPTY `attachments` array at that instant, and `buildCase` captures\n * `attachments.length > 0 ? attachments : undefined` as a plain\n * `undefined` VALUE right then, permanently — a later push onto the\n * (still-live) array reference would no longer be visible through\n * `undefined`. Awaiting here first guarantees `attachments` is complete\n * before `buildCase` ever reads it. */\n async finish(e: TestCaseFinished): Promise<FinishedAttempts | undefined> {\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n this.byTestCaseStartedId.delete(e.testCaseStartedId);\n if (!record) {\n return undefined;\n }\n if (e.willBeRetried) {\n return undefined;\n }\n\n const testCaseId = record.testCase.id;\n const attempts = this.byTestCaseId.get(testCaseId) ?? [record];\n this.byTestCaseId.delete(testCaseId);\n\n const pendingWrites = attempts.flatMap((a) => a.pendingVideoWrites);\n if (pendingWrites.length > 0) {\n await Promise.all(pendingWrites);\n }\n\n const snapshots: AttemptSnapshot[] = attempts.map((a) => {\n const worst = a.stepResults.length > 0 ? getWorstTestStepResult(a.stepResults) : undefined;\n const duration = a.stepResults.reduce((sum, r) => sum + messageDurationToNs(r.duration), 0);\n return {\n status: worst ? mapStatus(worst.status) : 'passed',\n duration,\n // `message` first — see `step-mapper.ts`'s `formatError()` doc\n // comment for why (verified empirically across the peer-dependency\n // range; `exception.stackTrace` is not version-safe).\n error: worst?.message || worst?.exception?.stackTrace || worst?.exception?.message,\n steps: a.steps,\n manualSteps: a.manualSteps,\n labels: a.labels,\n links: a.links,\n tags: a.tags,\n description: a.description,\n priority: a.priority,\n properties: a.properties,\n attachments: a.attachments,\n };\n });\n\n return {\n uri: record.pickle.uri,\n pickleId: record.testCase.pickleId,\n collapsed: collapseAttempts(snapshots),\n };\n }\n}\n\nfunction timestampMs(ts: { seconds: number; nanos: number }): number {\n return ts.seconds * 1000 + Math.floor(ts.nanos / 1_000_000);\n}\n","import type { NanosecondDuration } from './types.js';\n\nconst NS_PER_MS = 1_000_000;\nconst NS_PER_SECOND = 1_000_000_000;\n\n/**\n * Converts a plain millisecond duration (e.g. from `Date.now()` deltas used\n * by manual `qualflare.step()` timing) into the wire format's raw-nanosecond\n * integer (see `NanosecondDuration` in ./types.ts).\n *\n * Rounds (not truncates) so fractional-ms input doesn't lose precision by\n * always rounding toward zero. Negative input is clamped to 0 — a negative\n * duration is never legitimate and silently clamping is safer for an\n * ingest payload than throwing and aborting an otherwise-good report.\n */\nexport function msToNs(ms: number): NanosecondDuration {\n if (!Number.isFinite(ms) || ms <= 0) {\n return 0;\n }\n return Math.round(ms * NS_PER_MS);\n}\n\n/**\n * Converts a `@cucumber/messages` structured `Duration` ({seconds, nanos})\n * into the wire format's raw-nanosecond integer, without round-tripping\n * through milliseconds (which would lose sub-millisecond precision Cucumber\n * already reports natively). Negative/malformed input is clamped to 0, same\n * defensive posture as `msToNs`.\n */\nexport function messageDurationToNs(duration: { seconds: number; nanos: number } | undefined): NanosecondDuration {\n if (!duration || !Number.isFinite(duration.seconds) || !Number.isFinite(duration.nanos)) {\n return 0;\n }\n const total = duration.seconds * NS_PER_SECOND + duration.nanos;\n return total > 0 ? Math.round(total) : 0;\n}\n","import type { Pickle } from '@cucumber/messages';\n\nimport { MAX_TAGS_PER_CASE } from '../shared/constants.js';\nimport { msToNs } from '../shared/duration.js';\nimport type { ManualStepRecord } from '../runtime/message-types.js';\nimport type { Attachment, Case, CasePriority, CaseStatus, Label, Link, NanosecondDuration, Step } from '../shared/types.js';\nimport type { GherkinIndex } from './gherkin-index.js';\n\n/** One attempt of a scenario — cucumber-js's `--retry` re-runs the same\n * pickle from scratch (fresh World, all hooks/steps rerun); each attempt\n * (`testCaseStarted`→...→`testCaseFinished`) produces one of these. Ported\n * from `@qualflare/cypress`'s `AttemptSnapshot`/`collapseAttempts` — same\n * \"final attempt wins for content, but count+sum across all attempts\"\n * rules — fed by envelope-derived data here instead of Mocha-derived data. */\nexport interface AttemptSnapshot {\n status: CaseStatus;\n /** NANOSECONDS — already summed across this attempt's real step results;\n * see `attempt-tracker.ts`. No ms round-trip, unlike the Cypress version:\n * Cucumber's own `TestStepResult.duration` is nanosecond-precision. */\n duration: NanosecondDuration;\n error?: string;\n /** Real Gherkin + hook steps, already wire-shaped by `step-mapper.ts`. */\n steps: Step[];\n manualSteps: ManualStepRecord[];\n labels: Label[];\n links: Link[];\n /** Dynamically added via `qualflare.tag()` during this attempt — merged\n * with the pickle's own static `@tag`s separately, in `case-builder.ts`'s\n * `buildCase()`, since those are the same across every attempt. */\n tags: string[];\n description?: string;\n priority?: CasePriority;\n properties: Record<string, string>;\n attachments: Attachment[];\n}\n\nexport interface CollapsedResult {\n status: CaseStatus;\n /** NANOSECONDS — sum of every attempt (reflects true CI wall-clock cost,\n * not just the final attempt's duration). */\n duration: NanosecondDuration;\n retryCount: number;\n isFlaky: boolean;\n error?: string;\n /** Only the FINAL attempt's steps — an abandoned (retried) attempt's step\n * trace would misrepresent a single execution as if the same commands ran\n * twice, so earlier attempts' steps are discarded, never merged. */\n steps?: Step[];\n labels: Label[];\n links: Link[];\n tags: string[];\n description?: string;\n priority?: CasePriority;\n properties: Record<string, string>;\n attachments: Attachment[];\n}\n\n/** Appends `manualSteps` (from `qualflare.step()`, indices/`parentIndex`\n * valid only relative to each other) after `realSteps` (Gherkin + hook\n * steps, indices/`parentIndex` valid only relative to each other) into one\n * combined, correctly-indexed `Step[]`. Ported verbatim in spirit from\n * `@qualflare/cypress`'s `combineSteps()`. */\nfunction combineSteps(realSteps: Step[], manualSteps: ManualStepRecord[]): Step[] | undefined {\n if (realSteps.length === 0 && manualSteps.length === 0) {\n return undefined;\n }\n const offsetManual: Step[] = manualSteps.map((record) => {\n const step: Step = {\n name: record.name,\n status: record.status,\n duration: msToNs(record.durationMs ?? 0),\n };\n if (record.error) step.error = record.error;\n if (record.parentIndex !== undefined) step.parentIndex = record.parentIndex + realSteps.length;\n if (record.parameters && record.parameters.length > 0) step.parameters = record.parameters;\n return step;\n });\n return [...realSteps, ...offsetManual];\n}\n\n/**\n * Collapses every attempt of one logical scenario (grouped by cucumber-js's\n * own stable `testCase.id`, NOT a content hash — see `attempt-tracker.ts`)\n * into the single `Case` this reporter uploads. `willBeRetried === false` on\n * the final `testCaseFinished` is the caller's signal that all attempts have\n * arrived; this function only does the collapse.\n */\nexport function collapseAttempts(attempts: AttemptSnapshot[]): CollapsedResult {\n if (attempts.length === 0) {\n throw new Error('collapseAttempts: at least one attempt is required');\n }\n const final = attempts[attempts.length - 1]!;\n const retryCount = attempts.length - 1;\n const isFlaky = retryCount > 0 && final.status === 'passed' && attempts.some((a) => a.status !== 'passed');\n const duration = attempts.reduce((sum, a) => sum + a.duration, 0);\n\n return {\n status: final.status,\n duration,\n retryCount,\n isFlaky,\n error: final.status === 'passed' ? undefined : final.error,\n steps: combineSteps(final.steps, final.manualSteps),\n labels: final.labels,\n links: final.links,\n tags: final.tags,\n description: final.description,\n priority: final.priority,\n properties: final.properties,\n attachments: final.attachments,\n };\n}\n\nexport interface FinishedCase {\n uri: string;\n case: Case;\n}\n\n/** Assembles the final wire `Case` for one finished (all-attempts-collapsed)\n * scenario. */\nexport function buildCase(uri: string, pickle: Pickle, collapsed: CollapsedResult, gherkin: GherkinIndex): FinishedCase {\n const entry = gherkin.get(uri);\n // `pickle.astNodeIds` is `[scenarioId]` for a plain Scenario, or\n // `[scenarioId, exampleRowId]` for one row of a Scenario Outline — the\n // scenario's own AST id is always first.\n const scenarioId = pickle.astNodeIds[0];\n const scenarioEntry = scenarioId ? entry?.scenarioById.get(scenarioId) : undefined;\n const featureName = entry?.featureName;\n const ruleName = scenarioEntry?.ruleName;\n const className = ruleName ? `${featureName ?? ''} > ${ruleName}` : featureName;\n\n const labels: Label[] = [...collapsed.labels];\n if (featureName) {\n labels.push({ name: 'feature', value: featureName });\n }\n if (ruleName) {\n labels.push({ name: 'rule', value: ruleName });\n }\n\n const properties: Record<string, string> = { ...examplesRowProperties(scenarioEntry, pickle), ...collapsed.properties };\n\n // `pickle.tags` already has Feature/Rule `@tag`s inherited/flattened onto\n // every scenario by cucumber-js itself — no manual inheritance needed.\n const staticTags = pickle.tags.map((t) => t.name.replace(/^@/, ''));\n const tags = [...new Set([...staticTags, ...collapsed.tags])].slice(0, MAX_TAGS_PER_CASE);\n\n const kase: Case = {\n // Stable across runs (unchanged unless the file is edited), and\n // includes the source line specifically so two identically-named\n // Scenarios in one feature file can never collide — the exact bug\n // that's open upstream in allure-js (allure-framework/allure-js#1502).\n id: `${uri}:${pickle.location?.line ?? 0}#${pickle.name}`,\n name: pickle.name,\n status: collapsed.status,\n duration: collapsed.duration,\n retryCount: collapsed.retryCount || undefined,\n isFlaky: collapsed.isFlaky || undefined,\n error: collapsed.error,\n tags: tags.length > 0 ? tags : undefined,\n steps: collapsed.steps,\n labels: labels.length > 0 ? labels : undefined,\n links: collapsed.links.length > 0 ? collapsed.links : undefined,\n // `||`, not `??`: cucumber-js's `Scenario.description` is always a\n // defined string, `''` when the Gherkin source has none — `??` would\n // never fall through and every scenario without one would send an\n // empty-string description instead of omitting the field (found via a\n // real tarball-installed smoke test, not just reasoning about types).\n description: collapsed.description || scenarioEntry?.scenario.description || undefined,\n priority: collapsed.priority,\n properties: Object.keys(properties).length > 0 ? properties : undefined,\n attachments: collapsed.attachments.length > 0 ? collapsed.attachments : undefined,\n };\n if (className) {\n kase.className = className;\n }\n return { uri, case: kase };\n}\n\n/** For a Scenario Outline row, folds that row's concrete Examples values\n * into `Case.properties` (`{columnName: cellValue}`) — the same AST\n * `tableBody`-correlation technique verified in `allure-cucumberjs`'s real\n * source. Every row's values show up on that row's Case automatically, with\n * zero extra author effort. */\nfunction examplesRowProperties(\n scenarioEntry: { scenario: { examples: readonly { tableHeader?: { cells: readonly { value: string }[] }; tableBody: readonly { id: string; cells: readonly { value: string }[] }[] }[] } } | undefined,\n pickle: Pickle,\n): Record<string, string> {\n if (!scenarioEntry) {\n return {};\n }\n const rowAstIds = new Set(pickle.astNodeIds);\n for (const examples of scenarioEntry.scenario.examples) {\n const header = examples.tableHeader?.cells;\n if (!header) {\n continue;\n }\n const row = examples.tableBody.find((r) => rowAstIds.has(r.id));\n if (!row) {\n continue;\n }\n const properties: Record<string, string> = {};\n header.forEach((cell, i) => {\n const value = row.cells[i]?.value;\n if (cell.value && value !== undefined) {\n properties[cell.value] = value;\n }\n });\n return properties;\n }\n return {};\n}\n","import { TestStepResultStatus, type PickleStep, type PickleStepArgument, type TestStepResult } from '@cucumber/messages';\n\nimport { messageDurationToNs } from '../shared/duration.js';\nimport type { CaseStatus, Parameter, Step } from '../shared/types.js';\nimport type { GherkinIndex } from './gherkin-index.js';\nimport type { HookInfo } from './hook-index.js';\n\n/** Maps cucumber-js's step/scenario result status onto the wire contract's\n * `CaseStatus` vocabulary. `UNDEFINED` (no matching step definition) and\n * `AMBIGUOUS` (more than one matching definition) are both authoring/config\n * problems rather than a real pass/fail outcome — `'error'` is the closest\n * semantic fit in the wire vocabulary, distinct from a genuine `'failed'`\n * assertion. */\nexport function mapStatus(status: TestStepResultStatus): CaseStatus {\n switch (status) {\n case TestStepResultStatus.PASSED:\n return 'passed';\n case TestStepResultStatus.FAILED:\n return 'failed';\n case TestStepResultStatus.SKIPPED:\n return 'skipped';\n case TestStepResultStatus.PENDING:\n return 'pending';\n case TestStepResultStatus.UNDEFINED:\n case TestStepResultStatus.AMBIGUOUS:\n case TestStepResultStatus.UNKNOWN:\n default:\n return 'error';\n }\n}\n\n/** `TestStepResult.message` is the version-safe field to prefer: verified\n * empirically (real spawned runs, not just docs) that it reliably contains\n * the full \"Error: <message>\\n at ...\" text on both the peer floor\n * (cucumber-js 10.9.0) and the latest (13.2.1) — `exception.stackTrace`\n * does NOT: on 10.9.0 it's stack-frames only, with no message text at all,\n * while on 13.2.1 it happens to duplicate the full combined text. Preferring\n * `stackTrace` first (an earlier version of this function did) silently\n * produced a message-less error on 10.9.0 — caught by running the real CI\n * version matrix locally before trusting it, not by reasoning about the\n * schema alone. */\nfunction formatError(result: TestStepResult): string | undefined {\n return result.message || result.exception?.stackTrace || result.exception?.message;\n}\n\n/** Doc Strings and Data Tables have no dedicated field on the wire `Step`\n * contract (confirmed against `launch.go` — the only structured-payload\n * slot on a step is the flat `parameters` list) — encoded as one Parameter\n * each, documented as a workaround in `docs/LIMITATIONS.md`. A Data Table is\n * JSON-stringified as one Parameter rather than exploded into one Parameter\n * per cell, to avoid risking `MAX_PARAMETERS_PER_STEP` on a large table. */\nexport function pickleStepArgumentToParameters(argument: PickleStepArgument | undefined): Parameter[] | undefined {\n if (!argument) {\n return undefined;\n }\n const params: Parameter[] = [];\n if (argument.docString) {\n params.push({ name: 'docString', value: argument.docString.content });\n }\n if (argument.dataTable) {\n const rows = argument.dataTable.rows.map((row) => row.cells.map((cell) => cell.value));\n params.push({ name: 'dataTable', value: JSON.stringify(rows) });\n }\n return params.length > 0 ? params : undefined;\n}\n\n/** Builds a wire `Step` for a real Gherkin (Given/When/Then/And/But) step. */\nexport function mapPickleStep(\n uri: string,\n pickleStep: PickleStep,\n result: TestStepResult,\n gherkin: GherkinIndex,\n): Step {\n const resolved = gherkin.resolveKeyword(uri, pickleStep.astNodeIds);\n const step: Step = {\n name: pickleStep.text,\n status: mapStatus(result.status),\n duration: messageDurationToNs(result.duration),\n };\n if (resolved?.keyword) {\n step.keyword = resolved.keyword.trim();\n }\n const error = formatError(result);\n if (error) {\n step.error = error;\n }\n const parameters = pickleStepArgumentToParameters(pickleStep.argument);\n if (parameters) {\n step.parameters = parameters;\n }\n return step;\n}\n\n/** Builds a synthetic wire `Step` for a `Before`/`After` (or, when enabled,\n * `BeforeStep`/`AfterStep`) hook execution — see design decision (a) in the\n * plan: hooks are folded directly into the flat/nested `Step[]` rather than\n * needing a separate \"fixture\" model concept the way Allure's richer model\n * requires, since our wire contract has no such concept anyway. */\nconst HOOK_LABELS: Record<HookInfo['kind'], string> = {\n before: 'Before',\n after: 'After',\n beforeStep: 'BeforeStep',\n afterStep: 'AfterStep',\n beforeAll: 'BeforeAll',\n afterAll: 'AfterAll',\n};\n\nexport function mapHookStep(hook: HookInfo, result: TestStepResult): Step {\n // A distinct label per hook kind (not just \"Before\"/\"After\" for\n // everything) so a step-level hook is distinguishable from a case-level\n // one in the uploaded data when `includeStepHooks` is enabled.\n const label = HOOK_LABELS[hook.kind];\n const step: Step = {\n name: hook.name || `${label} hook`,\n keyword: label,\n status: mapStatus(result.status),\n duration: messageDurationToNs(result.duration),\n };\n const error = formatError(result);\n if (error) {\n step.error = error;\n }\n return step;\n}\n","import * as os from 'node:os';\n\nimport { PACKAGE_VERSION } from '../config/version.js';\nimport type { ResolvedFormatterConfig } from '../config/resolve-config.js';\nimport type { Collect, Suite } from '../shared/types.js';\n\nfunction resolveOs(config: ResolvedFormatterConfig): string {\n if (config.os) {\n return config.os;\n }\n return `${os.type()} ${os.release()}`;\n}\n\n/**\n * Assembles the final `Collect` payload from every finished `Suite`, at\n * `finished()`. CI metadata and branch/commit auto-detection are already\n * fully resolved by `resolve-config.ts` — this function just reads the\n * resolved config through, it does not call `ci-detect.ts`/`git-detect.ts`\n * itself. Unlike `@qualflare/cypress`'s version, there is no `BrowserInfo`\n * parameter — cucumber-js has no browser context of its own (unless a user\n * pairs it with a browser driver, which is outside this reporter's own\n * knowledge), so `browser` is config-only and `os` falls back to\n * `os.type()/os.release()`.\n */\nexport function buildCollectPayload(suites: Suite[], config: ResolvedFormatterConfig): Collect {\n return {\n framework: config.framework,\n platform: config.platform,\n os: resolveOs(config),\n browser: config.browser ?? '',\n branch: config.branch,\n commit: config.commit,\n environment: config.environment,\n language: config.language,\n milestone: config.milestone,\n metadata: {\n version: PACKAGE_VERSION,\n timestamp: new Date().toISOString(),\n cliName: 'qualflare-cucumberjs',\n },\n properties: config.properties,\n suites,\n ciProvider: config.ciProvider,\n ciBuildNumber: config.ciBuildNumber,\n ciRunUrl: config.ciRunUrl,\n ciPrNumber: config.ciPrNumber,\n };\n}\n","// `__PACKAGE_VERSION__` is injected at build time by tsup's `define` option\n// (see tsup.config.ts) from package.json's `version` field. Deliberately\n// NOT read at runtime via `import.meta.url` + `createRequire` — that breaks\n// the CJS build output (`import.meta` is empty/unavailable once esbuild\n// compiles to CommonJS), which is exactly the class of dual-CJS/ESM-package\n// bug a build-time constant sidesteps entirely. Under Vitest (which never\n// goes through tsup), `vitest.config.ts` defines the same constant so this\n// module behaves identically in tests and in the built package.\ndeclare const __PACKAGE_VERSION__: string;\n\nexport const PACKAGE_VERSION: string = __PACKAGE_VERSION__;\n","import type { GherkinDocument, Scenario } from '@cucumber/messages';\n\ninterface ScenarioEntry {\n scenario: Scenario;\n ruleName?: string;\n}\n\ninterface FeatureEntry {\n featureName?: string;\n /** AST step id -> literal Gherkin keyword (\"Given \"/\"When \"/\"Then \"/\"And \"/\n * \"But \", with cucumber-js's own trailing space) + step text. Background\n * steps are indexed here too — cucumber-js already merges Background\n * steps into every scenario's compiled `Pickle.steps[]` itself, so no\n * separate handling is needed; a Background step's AST id just needs to\n * resolve here like any other. */\n stepMap: Map<string, { keyword: string; text: string }>;\n scenarioById: Map<string, ScenarioEntry>;\n}\n\n/**\n * Indexes each `gherkinDocument` envelope (one per feature file) so\n * `case-builder.ts`/`step-mapper.ts` can resolve, per pickle: the Feature\n * name, an optional `Rule:` name, the Scenario AST node (for its\n * `examples[]`, used for Scenario Outline row correlation), and literal\n * Given/When/Then/And/But keyword text for each step (the compiled\n * `PickleStep.type` only gives a coarse Context/Action/Outcome/Unknown\n * classification, not the literal keyword).\n */\nexport class GherkinIndex {\n private readonly byUri = new Map<string, FeatureEntry>();\n\n add(doc: GherkinDocument): void {\n if (!doc.uri || !doc.feature) {\n return;\n }\n const entry: FeatureEntry = {\n featureName: doc.feature.name || undefined,\n stepMap: new Map(),\n scenarioById: new Map(),\n };\n for (const child of doc.feature.children) {\n if (child.background) {\n this.indexSteps(entry, child.background.steps);\n }\n if (child.scenario) {\n entry.scenarioById.set(child.scenario.id, { scenario: child.scenario });\n this.indexSteps(entry, child.scenario.steps);\n }\n if (child.rule) {\n for (const ruleChild of child.rule.children) {\n if (ruleChild.background) {\n this.indexSteps(entry, ruleChild.background.steps);\n }\n if (ruleChild.scenario) {\n entry.scenarioById.set(ruleChild.scenario.id, {\n scenario: ruleChild.scenario,\n ruleName: child.rule.name || undefined,\n });\n this.indexSteps(entry, ruleChild.scenario.steps);\n }\n }\n }\n }\n this.byUri.set(doc.uri, entry);\n }\n\n private indexSteps(entry: FeatureEntry, steps: readonly { id: string; keyword: string; text: string }[]): void {\n for (const step of steps) {\n entry.stepMap.set(step.id, { keyword: step.keyword, text: step.text });\n }\n }\n\n get(uri: string): FeatureEntry | undefined {\n return this.byUri.get(uri);\n }\n\n /** Resolves the literal Given/When/Then/And/But keyword text for a\n * compiled `PickleStep`, by walking its `astNodeIds` back to the first\n * one present in this feature's `stepMap` (a Background step referenced\n * by a scenario has exactly one AST id; a step reusing a parameter type\n * from an outline row can have more than one — the first match is always\n * the step's own defining AST node). */\n resolveKeyword(uri: string, astNodeIds: readonly string[]): { keyword: string; text: string } | undefined {\n const entry = this.byUri.get(uri);\n if (!entry) {\n return undefined;\n }\n for (const id of astNodeIds) {\n const found = entry.stepMap.get(id);\n if (found) {\n return found;\n }\n }\n return undefined;\n }\n}\n","import type { IFormatterOptions } from '@cucumber/cucumber';\n\n// `SupportCodeLibrary` itself isn't re-exported from the package's public\n// entry point (only reachable via a deep `lib/**` import, which the\n// package's exports map only allows for `require`, not `import` — this\n// package is ESM-first). Deriving the type via indexed access on the\n// already-public `IFormatterOptions` avoids that deep import entirely.\ntype SupportCodeLibrary = IFormatterOptions['supportCodeLibrary'];\n\nexport type HookKind = 'before' | 'after' | 'beforeStep' | 'afterStep' | 'beforeAll' | 'afterAll';\n\nexport interface HookInfo {\n kind: HookKind;\n name?: string;\n}\n\nexport type HookIndex = ReadonlyMap<string, HookInfo>;\n\n/**\n * Resolves a hook's kind from `supportCodeLibrary`'s six definition-array\n * fields, NOT the envelope's `Hook.type` — that field was only added in\n * cucumber-js 11.2.0 (confirmed via the project's own CHANGELOG), which is\n * after this package's peer floor (`>=10.8.0`). This is also exactly the\n * mechanism `allure-cucumberjs`'s real source uses for `Before`/`After`\n * (though it never indexes the `TestStep`/`TestRunHook` collections at all,\n * which is why it silently drops `BeforeStep`/`AfterStep` and never handles\n * `BeforeAll`/`AfterAll` — this package intentionally indexes all six).\n */\nexport function buildHookIndex(supportCodeLibrary: SupportCodeLibrary): HookIndex {\n const index = new Map<string, HookInfo>();\n for (const def of supportCodeLibrary.beforeTestCaseHookDefinitions) {\n index.set(def.id, { kind: 'before', name: def.name || undefined });\n }\n for (const def of supportCodeLibrary.afterTestCaseHookDefinitions) {\n index.set(def.id, { kind: 'after', name: def.name || undefined });\n }\n for (const def of supportCodeLibrary.beforeTestStepHookDefinitions) {\n index.set(def.id, { kind: 'beforeStep' });\n }\n for (const def of supportCodeLibrary.afterTestStepHookDefinitions) {\n index.set(def.id, { kind: 'afterStep' });\n }\n for (const def of supportCodeLibrary.beforeTestRunHookDefinitions) {\n index.set(def.id, { kind: 'beforeAll' });\n }\n for (const def of supportCodeLibrary.afterTestRunHookDefinitions) {\n index.set(def.id, { kind: 'afterAll' });\n }\n return index;\n}\n","import type { TestRunHookFinished, TestRunHookStarted } from '@cucumber/messages';\n\nimport { messageDurationToNs } from '../shared/duration.js';\nimport type { Case } from '../shared/types.js';\nimport type { HookIndex } from './hook-index.js';\nimport { mapStatus } from './step-mapper.js';\n\n/**\n * `BeforeAll`/`AfterAll` run outside any test case (`testRunHookStarted`/\n * `Finished`, never a `Case`) — `allure-cucumberjs` drops these entirely\n * (confirmed by reading its real source: no case for them anywhere in its\n * envelope-dispatch switch). This tracker does not: a FAILED run-hook\n * becomes a synthetic `Case` in a `(global hooks)` Suite (design decision\n * (b) in the plan); a passing one produces nothing — a passing `BeforeAll`\n * has no useful signal to report, and would be pure noise on every run.\n */\nexport class RunHookTracker {\n /** `testRunHookStartedId` -> the hook it started, so `finish()` can look\n * up its kind/name. `TestRunHookFinished.result.duration` already gives\n * an accurate duration directly — no start/finish timestamp delta needed. */\n private readonly started = new Map<string, string>();\n private readonly failed: Case[] = [];\n\n start(e: TestRunHookStarted): void {\n this.started.set(e.id, e.hookId);\n }\n\n finish(e: TestRunHookFinished, hookIndex: HookIndex): void {\n const hookId = this.started.get(e.testRunHookStartedId);\n this.started.delete(e.testRunHookStartedId);\n const status = mapStatus(e.result.status);\n if (status === 'passed' || status === 'skipped') {\n return;\n }\n const hook = hookId ? hookIndex.get(hookId) : undefined;\n const label = hook?.kind === 'afterAll' ? 'AfterAll hook' : 'BeforeAll hook';\n this.failed.push({\n id: `global-hook:${e.testRunHookStartedId}`,\n name: hook?.name || label,\n status,\n duration: messageDurationToNs(e.result.duration),\n // `result.message` first — see `step-mapper.ts`'s `formatError()` doc\n // comment for why (verified empirically to be the version-safe field\n // across the peer-dependency range; `exception.stackTrace` is not).\n error: e.result.message || e.result.exception?.stackTrace || e.result.exception?.message,\n });\n }\n\n /** Returns `undefined` if no run-hook failed — see the class doc comment\n * for why a passing BeforeAll/AfterAll produces no Case at all. */\n buildSuite(): { name: string; category: 'cucumber'; duration: number; cases: Case[] } | undefined {\n if (this.failed.length === 0) {\n return undefined;\n }\n return {\n name: '(global hooks)',\n category: 'cucumber',\n duration: this.failed.reduce((sum, c) => sum + c.duration, 0),\n cases: this.failed,\n };\n }\n}\n","import * as path from 'node:path';\n\nimport { MAX_CASES_PER_SUITE, MAX_SUITES_PER_LAUNCH } from '../shared/constants.js';\nimport { logger } from '../shared/logger.js';\nimport type { Case, Suite } from '../shared/types.js';\nimport type { FinishedCase } from './case-builder.js';\n\n/**\n * Groups every finished Case into one `Suite` per `.feature` file. Unlike\n * `@qualflare/cypress` (which can batch incrementally, one spec file at a\n * time, since Cypress runs specs sequentially in one process),\n * cucumber-js can interleave scenarios from different feature files even\n * without `--parallel`, and always runs the formatter in the coordinator\n * process either way (worker *threads* under `--parallel` ship envelopes\n * back to it) — so grouping happens once, at `finished()`, over the whole\n * run's flat list of finished cases.\n */\nexport function groupIntoSuites(cases: FinishedCase[], cwd: string, extraSuite?: Suite): Suite[] {\n const byUri = new Map<string, Case[]>();\n for (const { uri, case: kase } of cases) {\n const bucket = byUri.get(uri);\n if (bucket) {\n bucket.push(kase);\n } else {\n byUri.set(uri, [kase]);\n }\n }\n\n const suites: Suite[] = [];\n for (const [uri, kases] of byUri) {\n if (kases.length > MAX_CASES_PER_SUITE) {\n logger.warn(\n `feature file \"${uri}\" reported ${kases.length} scenarios — only the first ${MAX_CASES_PER_SUITE} will be uploaded (server cap).`,\n );\n }\n suites.push({\n name: relativizeUri(uri, cwd),\n category: 'cucumber',\n duration: kases.reduce((sum, c) => sum + c.duration, 0),\n cases: kases.slice(0, MAX_CASES_PER_SUITE),\n });\n }\n\n if (extraSuite) {\n suites.push(extraSuite);\n }\n\n if (suites.length > MAX_SUITES_PER_LAUNCH) {\n logger.warn(\n `this run reported ${suites.length} feature-file suites — only the first ${MAX_SUITES_PER_LAUNCH} will be uploaded (server cap).`,\n );\n }\n return suites.slice(0, MAX_SUITES_PER_LAUNCH);\n}\n\n/** cucumber-js's own `pickle.uri` is already relative to the invocation cwd\n * in the common case (confirmed empirically: a real run reports e.g.\n * `\"features/passing.feature\"`, not an absolute path or a `file://` URL) —\n * this only normalizes the rarer absolute-path/URL forms down to the same\n * shape. */\nfunction relativizeUri(uri: string, cwd: string): string {\n let normalized = uri;\n if (normalized.startsWith('file://')) {\n normalized = new URL(normalized).pathname;\n }\n if (path.isAbsolute(normalized)) {\n normalized = path.relative(cwd, normalized);\n }\n return normalized.split(path.sep).join('/');\n}\n"],"mappings":"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","names":["import_node_crypto","fs","path","name","firstEnv","name","fs","path","extension","import_messages","resolved","path"]}
|