@supatest/playwright-reporter 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/dist/index.cjs +900 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +869 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/retry.ts","../src/api-client.ts","../src/git-utils.ts","../src/uploader.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type {\n\tFullConfig,\n\tFullResult,\n\tReporter,\n\tSuite,\n\tTestCase,\n\tTestResult,\n\tTestStep,\n} from \"@playwright/test/reporter\";\nimport { SupatestApiClient } from \"./api-client.js\";\nimport { getLocalGitInfo } from \"./git-utils.js\";\nimport type {\n\tAnnotationInfo,\n\tAttachmentKind,\n\tAttachmentMeta,\n\tCreateRunRequest,\n\tEnvironmentInfo,\n\tErrorInfo,\n\tProjectConfig,\n\tReporterOptions,\n\tRunSummary,\n\tSourceLocation,\n\tStepInfo,\n\tTestOutcome,\n\tTestResultEntry,\n\tTestResultPayload,\n\tTestStatus,\n} from \"./types.js\";\nimport { AttachmentUploader } from \"./uploader.js\";\n\nconst DEFAULT_API_URL = \"https://api.supatest.dev\";\nconst DEFAULT_MAX_CONCURRENT_UPLOADS = 5;\nconst DEFAULT_RETRY_ATTEMPTS = 3;\nconst DEFAULT_TIMEOUT_MS = 30000;\nconst MAX_STDOUT_LINES = 100;\nconst MAX_STDERR_LINES = 100;\n\ntype TestInfo = {\n\tstatus: TestStatus;\n\toutcome: TestOutcome;\n\tretries: number;\n};\n\nexport default class SupatestPlaywrightReporter implements Reporter {\n\tprivate readonly options: ReporterOptions;\n\tprivate client!: SupatestApiClient;\n\tprivate uploader!: AttachmentUploader;\n\tprivate runId?: string;\n\tprivate uploadQueue: Promise<void>[] = [];\n\tprivate uploadLimit!: <T>(fn: () => Promise<T>) => Promise<T>;\n\tprivate startedAt?: string;\n\tprivate firstTestStartTime?: number;\n\tprivate firstFailureTime?: number;\n\tprivate testsProcessed = new Map<string, TestInfo>();\n\tprivate disabled = false;\n\tprivate config?: FullConfig;\n\tprivate rootDir?: string;\n\tprivate stepIdCounter = 0;\n\n\tconstructor(options: ReporterOptions = {} as ReporterOptions) {\n\t\tthis.options = {\n\t\t\tapiUrl: DEFAULT_API_URL,\n\t\t\tuploadAssets: true,\n\t\t\tmaxConcurrentUploads: DEFAULT_MAX_CONCURRENT_UPLOADS,\n\t\t\tretryAttempts: DEFAULT_RETRY_ATTEMPTS,\n\t\t\ttimeoutMs: DEFAULT_TIMEOUT_MS,\n\t\t\tdryRun: false,\n\t\t\t...options,\n\t\t};\n\t}\n\n\tasync onBegin(config: FullConfig, suite: Suite): Promise<void> {\n\t\tthis.config = config;\n\t\tthis.rootDir = config.rootDir;\n\n\t\t// Validate required options\n\t\tif (!this.options.projectId) {\n\t\t\tthis.options.projectId = process.env.SUPATEST_PROJECT_ID ?? \"\";\n\t\t}\n\t\tif (!this.options.apiKey) {\n\t\t\tthis.options.apiKey = process.env.SUPATEST_API_KEY ?? \"\";\n\t\t}\n\n\t\t// Check if we should run in dry-run mode\n\t\tif (process.env.SUPATEST_DRY_RUN === \"true\") {\n\t\t\tthis.options.dryRun = true;\n\t\t}\n\n\t\tif (!this.options.projectId || !this.options.apiKey) {\n\t\t\tif (!this.options.dryRun) {\n\t\t\t\tthis.logWarn(\n\t\t\t\t\t\"Missing projectId or apiKey. Set SUPATEST_PROJECT_ID and SUPATEST_API_KEY env vars, or pass them as options.\",\n\t\t\t\t);\n\t\t\t\tthis.disabled = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Initialize clients\n\t\tthis.client = new SupatestApiClient({\n\t\t\tapiKey: this.options.apiKey,\n\t\t\tapiUrl: this.options.apiUrl!,\n\t\t\ttimeoutMs: this.options.timeoutMs!,\n\t\t\tretryAttempts: this.options.retryAttempts!,\n\t\t\tdryRun: this.options.dryRun!,\n\t\t});\n\n\t\tthis.uploader = new AttachmentUploader({\n\t\t\tmaxConcurrent: this.options.maxConcurrentUploads!,\n\t\t\ttimeoutMs: this.options.timeoutMs!,\n\t\t\tdryRun: this.options.dryRun!,\n\t\t});\n\n\t\t// Initialize p-limit for upload queue\n\t\tconst pLimit = (await import(\"p-limit\")).default;\n\t\tthis.uploadLimit = pLimit(this.options.maxConcurrentUploads!);\n\n\t\tthis.startedAt = new Date().toISOString();\n\t\tconst allTests = suite.allTests();\n\n\t\t// Build project configs\n\t\tconst projects = this.buildProjectConfigs(config);\n\n\t\t// Count unique test files\n\t\tconst testFiles = new Set(allTests.map((t) => t.location.file));\n\n\t\ttry {\n\t\t\tconst runRequest: CreateRunRequest = {\n\t\t\t\tprojectId: this.options.projectId,\n\t\t\t\tstartedAt: this.startedAt,\n\t\t\t\tplaywright: {\n\t\t\t\t\tversion: config.version,\n\t\t\t\t\tworkers: config.workers,\n\t\t\t\t\tretries: config.projects?.[0]?.retries ?? 0,\n\t\t\t\t\tfullyParallel: config.fullyParallel,\n\t\t\t\t\tforbidOnly: config.forbidOnly,\n\t\t\t\t\tglobalTimeout: config.globalTimeout,\n\t\t\t\t\treportSlowTests: config.reportSlowTests\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tmax: config.reportSlowTests.max,\n\t\t\t\t\t\t\t\tthreshold: config.reportSlowTests.threshold,\n\t\t\t\t\t\t }\n\t\t\t\t\t\t: undefined,\n\t\t\t\t},\n\t\t\t\tprojects,\n\t\t\t\ttestStats: {\n\t\t\t\t\ttotalFiles: testFiles.size,\n\t\t\t\t\ttotalTests: allTests.length,\n\t\t\t\t\ttotalProjects: config.projects?.length ?? 0,\n\t\t\t\t},\n\t\t\t\tshard: config.shard\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tcurrent: config.shard.current,\n\t\t\t\t\t\t\ttotal: config.shard.total,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t\tenvironment: this.getEnvironmentInfo(config),\n\t\t\t\tgit: await this.getGitInfo(),\n\t\t\t\tconfigPath: config.configFile,\n\t\t\t\trootDir: config.rootDir,\n\t\t\t\ttestDir: config.projects?.[0]?.testDir,\n\t\t\t\toutputDir: config.projects?.[0]?.outputDir,\n\t\t\t};\n\n\t\t\tconst response = await this.client.createRun(runRequest);\n\n\t\t\tthis.runId = response.runId;\n\t\t\tthis.logInfo(\n\t\t\t\t`Run ${this.runId} started (${allTests.length} tests across ${testFiles.size} files, ${projects.length} projects)`,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthis.logWarn(`Failed to create run: ${this.getErrorMessage(error)}`);\n\t\t\tthis.disabled = true;\n\t\t}\n\t}\n\n\tonTestEnd(test: TestCase, result: TestResult): void {\n\t\tif (this.disabled || !this.runId) return;\n\n\t\t// Track first test start time\n\t\tif (!this.firstTestStartTime && result.startTime) {\n\t\t\tthis.firstTestStartTime = result.startTime.getTime();\n\t\t}\n\n\t\t// Track first failure time\n\t\tif (\n\t\t\t!this.firstFailureTime &&\n\t\t\t(result.status === \"failed\" || result.status === \"timedOut\")\n\t\t) {\n\t\t\tthis.firstFailureTime = Date.now();\n\t\t}\n\n\t\t// Fire and forget - returns immediately (non-blocking)\n\t\tconst promise = this.uploadLimit(() =>\n\t\t\tthis.processTestResult(test, result),\n\t\t);\n\t\tthis.uploadQueue.push(promise);\n\t}\n\n\tasync onEnd(result: FullResult): Promise<void> {\n\t\tif (this.disabled || !this.runId) {\n\t\t\tthis.logInfo(\"Reporter disabled, skipping onEnd\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Wait for all uploads to complete\n\t\tthis.logInfo(`Waiting for ${this.uploadQueue.length} pending uploads...`);\n\t\tconst results = await Promise.allSettled(this.uploadQueue);\n\n\t\t// Log any failures\n\t\tconst failures = results.filter((r) => r.status === \"rejected\");\n\t\tif (failures.length > 0) {\n\t\t\tthis.logWarn(`${failures.length} uploads failed`);\n\t\t}\n\n\t\t// Complete the run\n\t\tconst summary = this.computeSummary(result);\n\t\tconst endedAt = new Date().toISOString();\n\t\tconst startTime = this.startedAt ? new Date(this.startedAt).getTime() : 0;\n\n\t\ttry {\n\t\t\tawait this.client.completeRun(this.runId, {\n\t\t\t\tstatus: this.mapRunStatus(result.status),\n\t\t\t\tendedAt,\n\t\t\t\tsummary,\n\t\t\t\ttiming: {\n\t\t\t\t\ttotalDurationMs: Math.round(result.duration),\n\t\t\t\t\ttimeToFirstTest: this.firstTestStartTime\n\t\t\t\t\t\t? Math.round(this.firstTestStartTime - startTime)\n\t\t\t\t\t\t: undefined,\n\t\t\t\t\ttimeToFirstFailure: this.firstFailureTime\n\t\t\t\t\t\t? Math.round(this.firstFailureTime - startTime)\n\t\t\t\t\t\t: undefined,\n\t\t\t\t},\n\t\t\t});\n\t\t\tthis.logInfo(`Run ${this.runId} completed: ${result.status}`);\n\t\t} catch (error) {\n\t\t\tthis.logWarn(`Failed to complete run: ${this.getErrorMessage(error)}`);\n\t\t}\n\t}\n\n\tprivate async processTestResult(\n\t\ttest: TestCase,\n\t\tresult: TestResult,\n\t): Promise<void> {\n\t\tconst testId = this.getTestId(test);\n\t\tconst resultId = this.getResultId(testId, result.retry);\n\n\t\ttry {\n\t\t\tconst payload = this.buildTestPayload(test, result);\n\t\t\tawait this.client.submitTest(this.runId!, payload);\n\n\t\t\t// Upload attachments if enabled\n\t\t\tif (this.options.uploadAssets) {\n\t\t\t\tawait this.uploadAttachments(result, resultId);\n\t\t\t}\n\n\t\t\t// Track test outcome\n\t\t\tconst outcome = this.getTestOutcome(test, result);\n\t\t\tconst existing = this.testsProcessed.get(testId);\n\t\t\tthis.testsProcessed.set(testId, {\n\t\t\t\tstatus: result.status as TestStatus,\n\t\t\t\toutcome,\n\t\t\t\tretries: (existing?.retries ?? 0) + (result.retry > 0 ? 1 : 0),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.logWarn(\n\t\t\t\t`Failed to submit test ${test.title}: ${this.getErrorMessage(error)}`,\n\t\t\t);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate buildTestPayload(\n\t\ttest: TestCase,\n\t\tresult: TestResult,\n\t): TestResultPayload {\n\t\tconst testId = this.getTestId(test);\n\n\t\t// Extract tags\n\t\tconst tags = test.tags ?? [];\n\n\t\t// Build all annotations\n\t\tconst annotations = this.serializeAnnotations(test.annotations ?? []);\n\n\t\t// Build result entry with full data\n\t\tconst resultEntry: TestResultEntry = {\n\t\t\tresultId: this.getResultId(testId, result.retry),\n\t\t\tretry: result.retry,\n\t\t\tstatus: result.status as TestStatus,\n\t\t\tstartTime: result.startTime?.toISOString?.() ?? undefined,\n\t\t\tdurationMs: result.duration,\n\t\t\tworkerIndex: result.workerIndex,\n\t\t\tparallelIndex: result.parallelIndex,\n\t\t\terrors: this.serializeErrors(result.errors ?? []),\n\t\t\tsteps: this.serializeSteps(result.steps ?? []),\n\t\t\tannotations: this.serializeAnnotations(result.annotations ?? []),\n\t\t\tattachments: this.serializeAttachmentMeta(result.attachments ?? []),\n\t\t\tstdout: this.serializeOutput(result.stdout ?? [], MAX_STDOUT_LINES),\n\t\t\tstderr: this.serializeOutput(result.stderr ?? [], MAX_STDERR_LINES),\n\t\t};\n\n\t\t// Get project name from test's parent suite chain\n\t\tconst projectName = test.parent?.project()?.name ?? \"unknown\";\n\n\t\treturn {\n\t\t\ttestId,\n\t\t\tplaywrightId: test.id,\n\t\t\tfile: this.relativePath(test.location.file),\n\t\t\tlocation: {\n\t\t\t\tfile: this.relativePath(test.location.file),\n\t\t\t\tline: test.location.line,\n\t\t\t\tcolumn: test.location.column,\n\t\t\t},\n\t\t\ttitle: test.title,\n\t\t\ttitlePath: test.titlePath(),\n\t\t\ttags,\n\t\t\tannotations,\n\t\t\texpectedStatus: test.expectedStatus as TestStatus,\n\t\t\toutcome: this.getTestOutcome(test, result),\n\t\t\ttimeout: test.timeout,\n\t\t\tretries: test.retries,\n\t\t\trepeatEachIndex:\n\t\t\t\ttest.repeatEachIndex > 0 ? test.repeatEachIndex : undefined,\n\t\t\tstatus: result.status as TestStatus,\n\t\t\tdurationMs: result.duration,\n\t\t\tretryCount: result.retry,\n\t\t\tresults: [resultEntry],\n\t\t\tprojectName,\n\t\t};\n\t}\n\n\tprivate serializeSteps(steps: readonly TestStep[]): StepInfo[] {\n\t\treturn steps.map((step) => this.serializeStep(step));\n\t}\n\n\tprivate serializeStep(step: TestStep): StepInfo {\n\t\tconst stepId = `step_${++this.stepIdCounter}`;\n\n\t\treturn {\n\t\t\tstepId,\n\t\t\ttitle: step.title,\n\t\t\tcategory: step.category,\n\t\t\tstartTime: step.startTime?.toISOString?.() ?? undefined,\n\t\t\tdurationMs: step.duration,\n\t\t\tlocation: step.location\n\t\t\t\t? {\n\t\t\t\t\t\tfile: this.relativePath(step.location.file),\n\t\t\t\t\t\tline: step.location.line,\n\t\t\t\t\t\tcolumn: step.location.column,\n\t\t\t\t }\n\t\t\t\t: undefined,\n\t\t\terror: step.error ? this.serializeError(step.error) : undefined,\n\t\t\t// Recursively serialize nested steps\n\t\t\tsteps:\n\t\t\t\tstep.steps && step.steps.length > 0\n\t\t\t\t\t? this.serializeSteps(step.steps)\n\t\t\t\t\t: undefined,\n\t\t\t// Step attachments (from step.attachments if available)\n\t\t\tattachments: undefined, // Playwright doesn't expose step.attachments directly\n\t\t};\n\t}\n\n\tprivate serializeErrors(\n\t\terrors: TestResult[\"errors\"],\n\t): ErrorInfo[] {\n\t\treturn errors.map((e) => this.serializeError(e));\n\t}\n\n\tprivate serializeError(error: {\n\t\tmessage?: string;\n\t\tstack?: string;\n\t\tvalue?: unknown;\n\t\tlocation?: { file: string; line: number; column: number };\n\t}): ErrorInfo {\n\t\treturn {\n\t\t\tmessage: error.message,\n\t\t\tstack: error.stack,\n\t\t\tvalue: error.value !== undefined ? String(error.value) : undefined,\n\t\t\tlocation: error.location\n\t\t\t\t? {\n\t\t\t\t\t\tfile: this.relativePath(error.location.file),\n\t\t\t\t\t\tline: error.location.line,\n\t\t\t\t\t\tcolumn: error.location.column,\n\t\t\t\t }\n\t\t\t\t: undefined,\n\t\t\t// Could extract code snippet from file here if needed\n\t\t\tsnippet: undefined,\n\t\t};\n\t}\n\n\tprivate serializeAnnotations(\n\t\tannotations: readonly { type: string; description?: string }[],\n\t): AnnotationInfo[] {\n\t\treturn annotations.map((a) => ({\n\t\t\ttype: a.type,\n\t\t\tdescription: a.description,\n\t\t}));\n\t}\n\n\tprivate serializeAttachmentMeta(\n\t\tattachments: TestResult[\"attachments\"],\n\t): AttachmentMeta[] {\n\t\treturn attachments\n\t\t\t.filter((a) => a.path || a.body)\n\t\t\t.map((a) => ({\n\t\t\t\tname: a.name,\n\t\t\t\tfilename: a.path ? path.basename(a.path) : a.name,\n\t\t\t\tcontentType: a.contentType,\n\t\t\t\tsizeBytes: this.getAttachmentSize(a),\n\t\t\t\tkind: this.getAttachmentKind(a.name, a.contentType),\n\t\t\t}));\n\t}\n\n\tprivate serializeOutput(\n\t\toutput: (string | Buffer)[],\n\t\tmaxLines: number,\n\t): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const chunk of output) {\n\t\t\tconst str = chunk.toString();\n\t\t\tlines.push(...str.split(\"\\n\"));\n\t\t\tif (lines.length >= maxLines) break;\n\t\t}\n\t\treturn lines.slice(0, maxLines);\n\t}\n\n\tprivate getTestOutcome(test: TestCase, result: TestResult): TestOutcome {\n\t\t// Use Playwright's built-in outcome() if available\n\t\tif (typeof test.outcome === \"function\") {\n\t\t\treturn test.outcome() as TestOutcome;\n\t\t}\n\n\t\t// Fallback logic\n\t\tif (result.status === \"skipped\") return \"skipped\";\n\t\tif (result.status === test.expectedStatus) return \"expected\";\n\t\tif (result.status === \"passed\" && result.retry > 0) return \"flaky\";\n\t\treturn \"unexpected\";\n\t}\n\n\tprivate async uploadAttachments(\n\t\tresult: TestResult,\n\t\ttestResultId: string,\n\t): Promise<void> {\n\t\tconst attachments = (result.attachments ?? []).filter((a) => a.path);\n\t\tif (attachments.length === 0) return;\n\n\t\tconst meta: AttachmentMeta[] = attachments.map((a) => ({\n\t\t\tname: a.name,\n\t\t\tfilename: path.basename(a.path!),\n\t\t\tcontentType: a.contentType,\n\t\t\tsizeBytes: this.getFileSize(a.path!),\n\t\t\tkind: this.getAttachmentKind(a.name, a.contentType),\n\t\t}));\n\n\t\ttry {\n\t\t\tconst { uploads } = await this.client.signAttachments(this.runId!, {\n\t\t\t\ttestResultId,\n\t\t\t\tattachments: meta,\n\t\t\t});\n\n\t\t\tconst uploadItems = uploads.map((u, i) => ({\n\t\t\t\tsignedUrl: u.signedUrl,\n\t\t\t\tfilePath: attachments[i].path!,\n\t\t\t\tcontentType: attachments[i].contentType,\n\t\t\t}));\n\n\t\t\tconst results = await this.uploader.uploadBatch(uploadItems, uploads);\n\t\t\tconst failedCount = results.filter((r) => !r.success).length;\n\n\t\t\tif (failedCount > 0) {\n\t\t\t\tthis.logWarn(`${failedCount} attachment uploads failed`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.logWarn(\n\t\t\t\t`Failed to upload attachments: ${this.getErrorMessage(error)}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate buildProjectConfigs(config: FullConfig): ProjectConfig[] {\n\t\treturn (config.projects ?? []).map((project) => {\n\t\t\tconst use = project.use ?? {} as Record<string, unknown>;\n\t\t\treturn {\n\t\t\t\tname: project.name,\n\t\t\t\tbrowserName: use.browserName as string | undefined,\n\t\t\t\tchannel: use.channel as string | undefined,\n\t\t\t\tdeviceName: (use as Record<string, unknown>).deviceName as string | undefined,\n\t\t\t\tviewport: use.viewport\n\t\t\t\t\t? { width: (use.viewport as { width: number; height: number }).width, height: (use.viewport as { width: number; height: number }).height }\n\t\t\t\t\t: undefined,\n\t\t\t\tlocale: use.locale as string | undefined,\n\t\t\t\ttimezone: use.timezoneId as string | undefined,\n\t\t\t\tcolorScheme: use.colorScheme as ProjectConfig[\"colorScheme\"],\n\t\t\t\tisMobile: use.isMobile as boolean | undefined,\n\t\t\t\thasTouch: use.hasTouch as boolean | undefined,\n\t\t\t\ttestDir: project.testDir,\n\t\t\t\ttimeout: project.timeout,\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate getEnvironmentInfo(config: FullConfig): EnvironmentInfo {\n\t\tconst ciInfo = this.getCIInfo();\n\n\t\treturn {\n\t\t\tos: {\n\t\t\t\tplatform: os.platform(),\n\t\t\t\trelease: os.release(),\n\t\t\t\tarch: os.arch(),\n\t\t\t},\n\t\t\tnode: {\n\t\t\t\tversion: process.version,\n\t\t\t},\n\t\t\tmachine: {\n\t\t\t\tcpus: os.cpus().length,\n\t\t\t\tmemory: os.totalmem(),\n\t\t\t\thostname: os.hostname(),\n\t\t\t},\n\t\t\tplaywright: {\n\t\t\t\tversion: config.version,\n\t\t\t},\n\t\t\tci: ciInfo,\n\t\t};\n\t}\n\n\tprivate computeSummary(result: FullResult): RunSummary {\n\t\tlet passed = 0;\n\t\tlet failed = 0;\n\t\tlet flaky = 0;\n\t\tlet skipped = 0;\n\t\tlet timedOut = 0;\n\t\tlet interrupted = 0;\n\t\tlet expected = 0;\n\t\tlet unexpected = 0;\n\n\t\tfor (const [, testInfo] of this.testsProcessed) {\n\t\t\tswitch (testInfo.status) {\n\t\t\t\tcase \"passed\":\n\t\t\t\t\tpassed += 1;\n\t\t\t\t\tif (testInfo.retries > 0) flaky += 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"failed\":\n\t\t\t\t\tfailed += 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"skipped\":\n\t\t\t\t\tskipped += 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"timedOut\":\n\t\t\t\t\ttimedOut += 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"interrupted\":\n\t\t\t\t\tinterrupted += 1;\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Count outcomes\n\t\t\tif (testInfo.outcome === \"expected\") expected += 1;\n\t\t\telse if (testInfo.outcome === \"unexpected\") unexpected += 1;\n\t\t}\n\n\t\treturn {\n\t\t\ttotal: this.testsProcessed.size,\n\t\t\tpassed,\n\t\t\tfailed,\n\t\t\tflaky,\n\t\t\tskipped,\n\t\t\ttimedOut,\n\t\t\tinterrupted,\n\t\t\tdurationMs: Math.round(result.duration),\n\t\t\texpected,\n\t\t\tunexpected,\n\t\t};\n\t}\n\n\tprivate mapRunStatus(\n\t\tstatus: FullResult[\"status\"],\n\t): \"complete\" | \"errored\" | \"interrupted\" {\n\t\tswitch (status) {\n\t\t\tcase \"passed\":\n\t\t\t\treturn \"complete\";\n\t\t\tcase \"failed\":\n\t\t\tcase \"timedout\":\n\t\t\t\treturn \"errored\";\n\t\t\tcase \"interrupted\":\n\t\t\t\treturn \"interrupted\";\n\t\t\tdefault:\n\t\t\t\treturn \"complete\";\n\t\t}\n\t}\n\n\tprivate getTestId(test: TestCase): string {\n\t\tconst key = `${test.location.file}:${test.location.line}:${test.title}`;\n\t\treturn this.hashKey(key);\n\t}\n\n\tprivate getResultId(testId: string, retry: number): string {\n\t\treturn this.hashKey(`${testId}:${retry}`);\n\t}\n\n\tprivate hashKey(value: string): string {\n\t\treturn createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 12);\n\t}\n\n\tprivate relativePath(filePath: string): string {\n\t\tif (this.rootDir && filePath.startsWith(this.rootDir)) {\n\t\t\treturn filePath.slice(this.rootDir.length + 1);\n\t\t}\n\t\treturn filePath;\n\t}\n\n\tprivate getFileSize(filePath: string): number {\n\t\ttry {\n\t\t\treturn fs.statSync(filePath).size;\n\t\t} catch {\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tprivate getAttachmentSize(\n\t\tattachment: TestResult[\"attachments\"][number],\n\t): number {\n\t\tif (attachment.path) {\n\t\t\treturn this.getFileSize(attachment.path);\n\t\t}\n\t\tif (attachment.body) {\n\t\t\treturn attachment.body.length;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tprivate getAttachmentKind(name: string, contentType: string): AttachmentKind {\n\t\tif (name === \"video\" || contentType.startsWith(\"video/\")) return \"video\";\n\t\tif (name === \"trace\" || name.endsWith(\".zip\")) return \"trace\";\n\t\tif (name.includes(\"screenshot\") || contentType === \"image/png\")\n\t\t\treturn \"screenshot\";\n\t\tif (name === \"stdout\") return \"stdout\";\n\t\tif (name === \"stderr\") return \"stderr\";\n\t\treturn \"other\";\n\t}\n\n\tprivate async getGitInfo() {\n\t\t// Try CI environment variables first\n\t\tconst ciGitInfo = {\n\t\t\tbranch:\n\t\t\t\tprocess.env.GITHUB_REF_NAME ??\n\t\t\t\tprocess.env.GITHUB_HEAD_REF ??\n\t\t\t\tprocess.env.CI_COMMIT_BRANCH ??\n\t\t\t\tprocess.env.GITLAB_CI_COMMIT_BRANCH ??\n\t\t\t\tprocess.env.GIT_BRANCH,\n\t\t\tcommit:\n\t\t\t\tprocess.env.GITHUB_SHA ??\n\t\t\t\tprocess.env.CI_COMMIT_SHA ??\n\t\t\t\tprocess.env.GITLAB_CI_COMMIT_SHA ??\n\t\t\t\tprocess.env.GIT_COMMIT,\n\t\t\tcommitMessage:\n\t\t\t\tprocess.env.CI_COMMIT_MESSAGE ??\n\t\t\t\tprocess.env.GITLAB_CI_COMMIT_MESSAGE,\n\t\t\trepo:\n\t\t\t\tprocess.env.GITHUB_REPOSITORY ??\n\t\t\t\tprocess.env.CI_PROJECT_PATH ??\n\t\t\t\tprocess.env.GITLAB_CI_PROJECT_PATH ??\n\t\t\t\tprocess.env.GIT_REPO,\n\t\t\tauthor: process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_NAME,\n\t\t\tauthorEmail: process.env.GITLAB_USER_EMAIL,\n\t\t\ttag: process.env.GITHUB_REF_TYPE === \"tag\" ? process.env.GITHUB_REF_NAME : undefined,\n\t\t};\n\n\t\t// If we have CI git info, use it (prioritize CI env vars)\n\t\tif (ciGitInfo.branch || ciGitInfo.commit) {\n\t\t\treturn ciGitInfo;\n\t\t}\n\n\t\t// Fall back to local git for local development\n\t\tconst localGitInfo = await getLocalGitInfo(this.rootDir);\n\n\t\t// Merge CI info with local git info (CI takes precedence)\n\t\treturn {\n\t\t\tbranch: ciGitInfo.branch ?? localGitInfo.branch,\n\t\t\tcommit: ciGitInfo.commit ?? localGitInfo.commit,\n\t\t\tcommitMessage: ciGitInfo.commitMessage ?? localGitInfo.commitMessage,\n\t\t\trepo: ciGitInfo.repo ?? localGitInfo.repo,\n\t\t\tauthor: ciGitInfo.author ?? localGitInfo.author,\n\t\t\tauthorEmail: ciGitInfo.authorEmail ?? localGitInfo.authorEmail,\n\t\t\ttag: ciGitInfo.tag ?? localGitInfo.tag,\n\t\t\tdirty: localGitInfo.dirty,\n\t\t};\n\t}\n\n\tprivate getCIInfo(): EnvironmentInfo[\"ci\"] | undefined {\n\t\tif (process.env.GITHUB_ACTIONS) {\n\t\t\treturn {\n\t\t\t\tprovider: \"github-actions\",\n\t\t\t\trunId: process.env.GITHUB_RUN_ID,\n\t\t\t\tjobId: process.env.GITHUB_JOB,\n\t\t\t\tjobUrl: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`,\n\t\t\t\tbuildNumber: process.env.GITHUB_RUN_NUMBER,\n\t\t\t\tbranch: process.env.GITHUB_REF_NAME,\n\t\t\t\tpullRequest: process.env.GITHUB_EVENT_NAME === \"pull_request\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tnumber: process.env.GITHUB_PR_NUMBER ?? \"\",\n\t\t\t\t\t\t\turl: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${process.env.GITHUB_PR_NUMBER}`,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\tif (process.env.GITLAB_CI) {\n\t\t\treturn {\n\t\t\t\tprovider: \"gitlab-ci\",\n\t\t\t\trunId: process.env.CI_PIPELINE_ID,\n\t\t\t\tjobId: process.env.CI_JOB_ID,\n\t\t\t\tjobUrl: process.env.CI_JOB_URL,\n\t\t\t\tbuildNumber: process.env.CI_PIPELINE_IID,\n\t\t\t\tbranch: process.env.CI_COMMIT_BRANCH,\n\t\t\t\tpullRequest: process.env.CI_MERGE_REQUEST_IID\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tnumber: process.env.CI_MERGE_REQUEST_IID,\n\t\t\t\t\t\t\turl: process.env.CI_MERGE_REQUEST_PROJECT_URL + \"/-/merge_requests/\" + process.env.CI_MERGE_REQUEST_IID,\n\t\t\t\t\t\t\ttitle: process.env.CI_MERGE_REQUEST_TITLE,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\tif (process.env.JENKINS_URL) {\n\t\t\treturn {\n\t\t\t\tprovider: \"jenkins\",\n\t\t\t\trunId: process.env.BUILD_ID,\n\t\t\t\tjobUrl: process.env.BUILD_URL,\n\t\t\t\tbuildNumber: process.env.BUILD_NUMBER,\n\t\t\t\tbranch: process.env.GIT_BRANCH,\n\t\t\t};\n\t\t}\n\t\tif (process.env.CIRCLECI) {\n\t\t\treturn {\n\t\t\t\tprovider: \"circleci\",\n\t\t\t\trunId: process.env.CIRCLE_WORKFLOW_ID,\n\t\t\t\tjobId: process.env.CIRCLE_JOB,\n\t\t\t\tjobUrl: process.env.CIRCLE_BUILD_URL,\n\t\t\t\tbuildNumber: process.env.CIRCLE_BUILD_NUM,\n\t\t\t\tbranch: process.env.CIRCLE_BRANCH,\n\t\t\t\tpullRequest: process.env.CIRCLE_PULL_REQUEST\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tnumber: process.env.CIRCLE_PR_NUMBER ?? \"\",\n\t\t\t\t\t\t\turl: process.env.CIRCLE_PULL_REQUEST,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\tif (process.env.TRAVIS) {\n\t\t\treturn {\n\t\t\t\tprovider: \"travis\",\n\t\t\t\trunId: process.env.TRAVIS_BUILD_ID,\n\t\t\t\tjobId: process.env.TRAVIS_JOB_ID,\n\t\t\t\tjobUrl: process.env.TRAVIS_JOB_WEB_URL,\n\t\t\t\tbuildNumber: process.env.TRAVIS_BUILD_NUMBER,\n\t\t\t\tbranch: process.env.TRAVIS_BRANCH,\n\t\t\t\tpullRequest: process.env.TRAVIS_PULL_REQUEST !== \"false\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tnumber: process.env.TRAVIS_PULL_REQUEST ?? \"\",\n\t\t\t\t\t\t\turl: `https://github.com/${process.env.TRAVIS_REPO_SLUG}/pull/${process.env.TRAVIS_PULL_REQUEST}`,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\tif (process.env.BUILDKITE) {\n\t\t\treturn {\n\t\t\t\tprovider: \"buildkite\",\n\t\t\t\trunId: process.env.BUILDKITE_BUILD_ID,\n\t\t\t\tjobId: process.env.BUILDKITE_JOB_ID,\n\t\t\t\tjobUrl: process.env.BUILDKITE_BUILD_URL,\n\t\t\t\tbuildNumber: process.env.BUILDKITE_BUILD_NUMBER,\n\t\t\t\tbranch: process.env.BUILDKITE_BRANCH,\n\t\t\t};\n\t\t}\n\t\tif (process.env.AZURE_PIPELINES || process.env.TF_BUILD) {\n\t\t\treturn {\n\t\t\t\tprovider: \"azure-pipelines\",\n\t\t\t\trunId: process.env.BUILD_BUILDID,\n\t\t\t\tjobUrl: `${process.env.SYSTEM_COLLECTIONURI}${process.env.SYSTEM_TEAMPROJECT}/_build/results?buildId=${process.env.BUILD_BUILDID}`,\n\t\t\t\tbuildNumber: process.env.BUILD_BUILDNUMBER,\n\t\t\t\tbranch: process.env.BUILD_SOURCEBRANCH,\n\t\t\t};\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate getErrorMessage(error: unknown): string {\n\t\tif (error instanceof Error) return error.message;\n\t\treturn String(error);\n\t}\n\n\tprivate logInfo(message: string): void {\n\t\tconsole.log(`[supatest] ${message}`);\n\t}\n\n\tprivate logWarn(message: string): void {\n\t\tconsole.warn(`[supatest] ${message}`);\n\t}\n}\n\n// Re-export types for consumers\nexport type { ReporterOptions } from \"./types.js\";\n","export type RetryConfig = {\n\tmaxAttempts: number;\n\tbaseDelayMs: number;\n\tmaxDelayMs: number;\n\tretryOnStatusCodes: number[];\n};\n\nexport const defaultRetryConfig: RetryConfig = {\n\tmaxAttempts: 3,\n\tbaseDelayMs: 1000,\n\tmaxDelayMs: 10000,\n\tretryOnStatusCodes: [408, 429, 500, 502, 503, 504],\n};\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction shouldRetry(error: unknown, config: RetryConfig): boolean {\n\tif (error instanceof Error && \"statusCode\" in error) {\n\t\tconst statusCode = (error as Error & { statusCode: number }).statusCode;\n\t\treturn config.retryOnStatusCodes.includes(statusCode);\n\t}\n\t// Network errors should be retried\n\tif (error instanceof Error) {\n\t\tconst networkErrors = [\"ECONNRESET\", \"ETIMEDOUT\", \"ECONNREFUSED\", \"ENOTFOUND\"];\n\t\treturn networkErrors.some((e) => error.message.includes(e));\n\t}\n\treturn false;\n}\n\nexport async function withRetry<T>(\n\tfn: () => Promise<T>,\n\tconfig: RetryConfig = defaultRetryConfig,\n): Promise<T> {\n\tlet lastError: unknown;\n\n\tfor (let attempt = 1; attempt <= config.maxAttempts; attempt++) {\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} catch (error) {\n\t\t\tlastError = error;\n\n\t\t\tif (attempt === config.maxAttempts) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (!shouldRetry(error, config)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tconst delay = Math.min(\n\t\t\t\tconfig.baseDelayMs * Math.pow(2, attempt - 1),\n\t\t\t\tconfig.maxDelayMs,\n\t\t\t);\n\n\t\t\tawait sleep(delay);\n\t\t}\n\t}\n\n\tthrow lastError;\n}\n","import { defaultRetryConfig, type RetryConfig, withRetry } from \"./retry.js\";\nimport type {\n\tCompleteRunRequest,\n\tCreateRunRequest,\n\tCreateRunResponse,\n\tSignAttachmentsRequest,\n\tSignAttachmentsResponse,\n\tTestResultPayload,\n} from \"./types.js\";\n\nexport type ApiClientOptions = {\n\tapiKey: string;\n\tapiUrl: string;\n\ttimeoutMs: number;\n\tretryAttempts: number;\n\tdryRun: boolean;\n};\n\nexport class SupatestApiClient {\n\tprivate readonly options: ApiClientOptions;\n\tprivate readonly retryConfig: RetryConfig;\n\n\tconstructor(options: ApiClientOptions) {\n\t\tthis.options = options;\n\t\tthis.retryConfig = {\n\t\t\t...defaultRetryConfig,\n\t\t\tmaxAttempts: options.retryAttempts,\n\t\t};\n\t}\n\n\tasync createRun(data: CreateRunRequest): Promise<CreateRunResponse> {\n\t\tif (this.options.dryRun) {\n\t\t\tthis.logPayload(\"POST /v1/runs\", data);\n\t\t\t// Return mock response for dry run\n\t\t\treturn {\n\t\t\t\trunId: `mock_run_${Date.now()}`,\n\t\t\t\tstatus: \"running\",\n\t\t\t};\n\t\t}\n\n\t\treturn this.request<CreateRunResponse>(\"POST\", \"/v1/runs\", data);\n\t}\n\n\tasync submitTest(runId: string, data: TestResultPayload): Promise<void> {\n\t\tif (this.options.dryRun) {\n\t\t\tthis.logPayload(`POST /v1/runs/${runId}/tests`, data);\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.request(\"POST\", `/v1/runs/${runId}/tests`, data);\n\t}\n\n\tasync signAttachments(\n\t\trunId: string,\n\t\tdata: SignAttachmentsRequest,\n\t): Promise<SignAttachmentsResponse> {\n\t\tif (this.options.dryRun) {\n\t\t\tthis.logPayload(`POST /v1/runs/${runId}/attachments/sign`, data);\n\t\t\t// Return mock signed URLs for dry run\n\t\t\treturn {\n\t\t\t\tuploads: data.attachments.map((att, i) => ({\n\t\t\t\t\tattachmentId: `mock_att_${i}_${Date.now()}`,\n\t\t\t\t\tsignedUrl: `https://mock-s3.example.com/uploads/${att.filename}`,\n\t\t\t\t\texpiresAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(),\n\t\t\t\t})),\n\t\t\t};\n\t\t}\n\n\t\treturn this.request<SignAttachmentsResponse>(\n\t\t\t\"POST\",\n\t\t\t`/v1/runs/${runId}/attachments/sign`,\n\t\t\tdata,\n\t\t);\n\t}\n\n\tasync completeRun(runId: string, data: CompleteRunRequest): Promise<void> {\n\t\tif (this.options.dryRun) {\n\t\t\tthis.logPayload(`POST /v1/runs/${runId}/complete`, data);\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.request(\"POST\", `/v1/runs/${runId}/complete`, data);\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${this.options.apiUrl}${path}`;\n\n\t\treturn withRetry(async () => {\n\t\t\tconst controller = new AbortController();\n\t\t\tconst timeoutId = setTimeout(\n\t\t\t\t() => controller.abort(),\n\t\t\t\tthis.options.timeoutMs,\n\t\t\t);\n\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(url, {\n\t\t\t\t\tmethod,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\tAuthorization: `Bearer ${this.options.apiKey}`,\n\t\t\t\t\t},\n\t\t\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t});\n\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst error = new Error(\n\t\t\t\t\t\t`API request failed: ${response.status} ${response.statusText}`,\n\t\t\t\t\t) as Error & { statusCode: number };\n\t\t\t\t\terror.statusCode = response.status;\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\n\t\t\t\tconst text = await response.text();\n\t\t\t\treturn text ? (JSON.parse(text) as T) : ({} as T);\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timeoutId);\n\t\t\t}\n\t\t}, this.retryConfig);\n\t}\n\n\tprivate logPayload(endpoint: string, data: unknown): void {\n\t\tconsole.log(`\\n[supatest][dry-run] ${endpoint}`);\n\t\tconsole.log(JSON.stringify(data, null, 2));\n\t}\n}\n","import { SimpleGit, simpleGit } from \"simple-git\";\n\nexport type GitInfo = {\n\tbranch?: string;\n\tcommit?: string;\n\tcommitMessage?: string;\n\trepo?: string;\n\tauthor?: string;\n\tauthorEmail?: string;\n\ttag?: string;\n\tdirty?: boolean;\n};\n\n/**\n * Get git information from the local repository\n * Falls back gracefully if git is not available or not a git repo\n */\nexport async function getLocalGitInfo(rootDir?: string): Promise<GitInfo> {\n\ttry {\n\t\tconst git: SimpleGit = simpleGit(rootDir || process.cwd());\n\n\t\t// Check if it's a git repository\n\t\tconst isRepo = await git.checkIsRepo();\n\t\tif (!isRepo) {\n\t\t\treturn {};\n\t\t}\n\n\t\tconst [branchResult, commitResult, status, remotes] = await Promise.all([\n\t\t\tgit.revparse([\"--abbrev-ref\", \"HEAD\"]).catch(() => undefined),\n\t\t\tgit.revparse([\"HEAD\"]).catch(() => undefined),\n\t\t\tgit.status().catch(() => undefined),\n\t\t\tgit.getRemotes(true).catch(() => []),\n\t\t]);\n\n\t\tconst branch = typeof branchResult === \"string\" ? branchResult.trim() : undefined;\n\t\tconst commit = typeof commitResult === \"string\" ? commitResult.trim() : undefined;\n\n\t\t// Get commit message (subject line)\n\t\tlet commitMessage: string | undefined;\n\t\ttry {\n\t\t\tconst logOutput = await git.raw([\"log\", \"-1\", \"--format=%s\"]);\n\t\t\tcommitMessage = typeof logOutput === \"string\" ? logOutput.trim() : undefined;\n\t\t} catch {\n\t\t\t// Ignore error\n\t\t}\n\n\t\t// Get author info from config\n\t\tlet author: string | undefined;\n\t\tlet authorEmail: string | undefined;\n\t\ttry {\n\t\t\tconst authorConfig = await git\n\t\t\t\t.getConfig(\"user.name\")\n\t\t\t\t.catch(() => undefined);\n\t\t\tconst emailConfig = await git\n\t\t\t\t.getConfig(\"user.email\")\n\t\t\t\t.catch(() => undefined);\n\t\t\tauthor =\n\t\t\t\tauthorConfig && typeof (authorConfig as any).value === \"string\"\n\t\t\t\t\t? (authorConfig as any).value.trim()\n\t\t\t\t\t: undefined;\n\t\t\tauthorEmail =\n\t\t\t\temailConfig && typeof (emailConfig as any).value === \"string\"\n\t\t\t\t\t? (emailConfig as any).value.trim()\n\t\t\t\t\t: undefined;\n\t\t} catch {\n\t\t\t// Ignore error\n\t\t}\n\n\t\t// Get current tag if on a tag\n\t\tlet tag: string | undefined;\n\t\ttry {\n\t\t\tconst tagResult = await git.raw([\"describe\", \"--tags\", \"--exact-match\"]).catch(() => undefined);\n\t\t\ttag = typeof tagResult === \"string\" ? tagResult.trim() : undefined;\n\t\t} catch {\n\t\t\t// Ignore error - not on a tag\n\t\t}\n\n\t\t// Get repository URL\n\t\tlet repo: string | undefined;\n\t\tconst remote = remotes && remotes.length > 0 ? remotes[0] : null;\n\t\tif (remote && remote.refs && remote.refs.fetch) {\n\t\t\trepo = remote.refs.fetch;\n\t\t}\n\n\t\t// Check for uncommitted changes\n\t\tconst dirty = status ? status.modified.length > 0 || status.created.length > 0 || status.deleted.length > 0 : false;\n\n\t\treturn {\n\t\t\tbranch,\n\t\t\tcommit,\n\t\t\tcommitMessage,\n\t\t\trepo,\n\t\t\tauthor,\n\t\t\tauthorEmail,\n\t\t\ttag,\n\t\t\tdirty,\n\t\t};\n\t} catch (error) {\n\t\t// If anything goes wrong, return empty object\n\t\t// This is a best-effort operation\n\t\treturn {};\n\t}\n}\n","import fs from \"node:fs\";\nimport { defaultRetryConfig, withRetry } from \"./retry.js\";\nimport type { SignedUpload, UploadItem, UploadResult } from \"./types.js\";\n\nexport type UploaderOptions = {\n\tmaxConcurrent: number;\n\ttimeoutMs: number;\n\tdryRun: boolean;\n};\n\nexport class AttachmentUploader {\n\tprivate readonly options: UploaderOptions;\n\n\tconstructor(options: UploaderOptions) {\n\t\tthis.options = options;\n\t}\n\n\tasync upload(\n\t\tsignedUrl: string,\n\t\tfilePath: string,\n\t\tcontentType: string,\n\t): Promise<void> {\n\t\tif (this.options.dryRun) {\n\t\t\tconst stats = fs.statSync(filePath);\n\t\t\tconsole.log(\n\t\t\t\t`[supatest][dry-run] Would upload ${filePath} (${stats.size} bytes) to S3`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst fileBuffer = await fs.promises.readFile(filePath);\n\n\t\tawait withRetry(async () => {\n\t\t\tconst controller = new AbortController();\n\t\t\tconst timeoutId = setTimeout(\n\t\t\t\t() => controller.abort(),\n\t\t\t\tthis.options.timeoutMs,\n\t\t\t);\n\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(signedUrl, {\n\t\t\t\t\tmethod: \"PUT\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": contentType,\n\t\t\t\t\t\t\"Content-Length\": String(fileBuffer.length),\n\t\t\t\t\t},\n\t\t\t\t\tbody: fileBuffer,\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t});\n\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst error = new Error(\n\t\t\t\t\t\t`S3 upload failed: ${response.status} ${response.statusText}`,\n\t\t\t\t\t) as Error & { statusCode: number };\n\t\t\t\t\terror.statusCode = response.status;\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timeoutId);\n\t\t\t}\n\t\t}, defaultRetryConfig);\n\t}\n\n\tasync uploadBatch(\n\t\titems: UploadItem[],\n\t\tsignedUploads: SignedUpload[],\n\t): Promise<UploadResult[]> {\n\t\t// Use dynamic import for p-limit (ESM module)\n\t\tconst pLimit = (await import(\"p-limit\")).default;\n\t\tconst limit = pLimit(this.options.maxConcurrent);\n\n\t\tconst results = await Promise.allSettled(\n\t\t\titems.map((item, index) =>\n\t\t\t\tlimit(async () => {\n\t\t\t\t\tawait this.upload(item.signedUrl, item.filePath, item.contentType);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tattachmentId: signedUploads[index]?.attachmentId,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t),\n\t\t);\n\n\t\treturn results.map((result, index) => {\n\t\t\tif (result.status === \"fulfilled\") {\n\t\t\t\treturn result.value;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tattachmentId: signedUploads[index]?.attachmentId,\n\t\t\t\terror: result.reason instanceof Error ? result.reason.message : String(result.reason),\n\t\t\t};\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAA2B;AAC3B,IAAAA,kBAAe;AACf,qBAAe;AACf,uBAAiB;;;ACIV,IAAM,qBAAkC;AAAA,EAC9C,aAAa;AAAA,EACb,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,oBAAoB,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAClD;AAEA,SAAS,MAAM,IAA2B;AACzC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACxD;AAEA,SAAS,YAAY,OAAgB,QAA8B;AAClE,MAAI,iBAAiB,SAAS,gBAAgB,OAAO;AACpD,UAAM,aAAc,MAAyC;AAC7D,WAAO,OAAO,mBAAmB,SAAS,UAAU;AAAA,EACrD;AAEA,MAAI,iBAAiB,OAAO;AAC3B,UAAM,gBAAgB,CAAC,cAAc,aAAa,gBAAgB,WAAW;AAC7E,WAAO,cAAc,KAAK,CAAC,MAAM,MAAM,QAAQ,SAAS,CAAC,CAAC;AAAA,EAC3D;AACA,SAAO;AACR;AAEA,eAAsB,UACrB,IACA,SAAsB,oBACT;AACb,MAAI;AAEJ,WAAS,UAAU,GAAG,WAAW,OAAO,aAAa,WAAW;AAC/D,QAAI;AACH,aAAO,MAAM,GAAG;AAAA,IACjB,SAAS,OAAO;AACf,kBAAY;AAEZ,UAAI,YAAY,OAAO,aAAa;AACnC,cAAM;AAAA,MACP;AAEA,UAAI,CAAC,YAAY,OAAO,MAAM,GAAG;AAChC,cAAM;AAAA,MACP;AAEA,YAAM,QAAQ,KAAK;AAAA,QAClB,OAAO,cAAc,KAAK,IAAI,GAAG,UAAU,CAAC;AAAA,QAC5C,OAAO;AAAA,MACR;AAEA,YAAM,MAAM,KAAK;AAAA,IAClB;AAAA,EACD;AAEA,QAAM;AACP;;;AC3CO,IAAM,oBAAN,MAAwB;AAAA,EACb;AAAA,EACA;AAAA,EAEjB,YAAY,SAA2B;AACtC,SAAK,UAAU;AACf,SAAK,cAAc;AAAA,MAClB,GAAG;AAAA,MACH,aAAa,QAAQ;AAAA,IACtB;AAAA,EACD;AAAA,EAEA,MAAM,UAAU,MAAoD;AACnE,QAAI,KAAK,QAAQ,QAAQ;AACxB,WAAK,WAAW,iBAAiB,IAAI;AAErC,aAAO;AAAA,QACN,OAAO,YAAY,KAAK,IAAI,CAAC;AAAA,QAC7B,QAAQ;AAAA,MACT;AAAA,IACD;AAEA,WAAO,KAAK,QAA2B,QAAQ,YAAY,IAAI;AAAA,EAChE;AAAA,EAEA,MAAM,WAAW,OAAe,MAAwC;AACvE,QAAI,KAAK,QAAQ,QAAQ;AACxB,WAAK,WAAW,iBAAiB,KAAK,UAAU,IAAI;AACpD;AAAA,IACD;AAEA,UAAM,KAAK,QAAQ,QAAQ,YAAY,KAAK,UAAU,IAAI;AAAA,EAC3D;AAAA,EAEA,MAAM,gBACL,OACA,MACmC;AACnC,QAAI,KAAK,QAAQ,QAAQ;AACxB,WAAK,WAAW,iBAAiB,KAAK,qBAAqB,IAAI;AAE/D,aAAO;AAAA,QACN,SAAS,KAAK,YAAY,IAAI,CAAC,KAAK,OAAO;AAAA,UAC1C,cAAc,YAAY,CAAC,IAAI,KAAK,IAAI,CAAC;AAAA,UACzC,WAAW,uCAAuC,IAAI,QAAQ;AAAA,UAC9D,WAAW,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,GAAI,EAAE,YAAY;AAAA,QAC9D,EAAE;AAAA,MACH;AAAA,IACD;AAEA,WAAO,KAAK;AAAA,MACX;AAAA,MACA,YAAY,KAAK;AAAA,MACjB;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,YAAY,OAAe,MAAyC;AACzE,QAAI,KAAK,QAAQ,QAAQ;AACxB,WAAK,WAAW,iBAAiB,KAAK,aAAa,IAAI;AACvD;AAAA,IACD;AAEA,UAAM,KAAK,QAAQ,QAAQ,YAAY,KAAK,aAAa,IAAI;AAAA,EAC9D;AAAA,EAEA,MAAc,QACb,QACAC,OACA,MACa;AACb,UAAM,MAAM,GAAG,KAAK,QAAQ,MAAM,GAAGA,KAAI;AAEzC,WAAO,UAAU,YAAY;AAC5B,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,YAAY;AAAA,QACjB,MAAM,WAAW,MAAM;AAAA,QACvB,KAAK,QAAQ;AAAA,MACd;AAEA,UAAI;AACH,cAAM,WAAW,MAAM,MAAM,KAAK;AAAA,UACjC;AAAA,UACA,SAAS;AAAA,YACR,gBAAgB;AAAA,YAChB,eAAe,UAAU,KAAK,QAAQ,MAAM;AAAA,UAC7C;AAAA,UACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,UACpC,QAAQ,WAAW;AAAA,QACpB,CAAC;AAED,YAAI,CAAC,SAAS,IAAI;AACjB,gBAAM,QAAQ,IAAI;AAAA,YACjB,uBAAuB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,UAC9D;AACA,gBAAM,aAAa,SAAS;AAC5B,gBAAM;AAAA,QACP;AAEA,cAAM,OAAO,MAAM,SAAS,KAAK;AACjC,eAAO,OAAQ,KAAK,MAAM,IAAI,IAAW,CAAC;AAAA,MAC3C,UAAE;AACD,qBAAa,SAAS;AAAA,MACvB;AAAA,IACD,GAAG,KAAK,WAAW;AAAA,EACpB;AAAA,EAEQ,WAAW,UAAkB,MAAqB;AACzD,YAAQ,IAAI;AAAA,sBAAyB,QAAQ,EAAE;AAC/C,YAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,EAC1C;AACD;;;ACjIA,wBAAqC;AAiBrC,eAAsB,gBAAgB,SAAoC;AACzE,MAAI;AACH,UAAM,UAAiB,6BAAU,WAAW,QAAQ,IAAI,CAAC;AAGzD,UAAM,SAAS,MAAM,IAAI,YAAY;AACrC,QAAI,CAAC,QAAQ;AACZ,aAAO,CAAC;AAAA,IACT;AAEA,UAAM,CAAC,cAAc,cAAc,QAAQ,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,MACvE,IAAI,SAAS,CAAC,gBAAgB,MAAM,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,MAC5D,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,MAC5C,IAAI,OAAO,EAAE,MAAM,MAAM,MAAS;AAAA,MAClC,IAAI,WAAW,IAAI,EAAE,MAAM,MAAM,CAAC,CAAC;AAAA,IACpC,CAAC;AAED,UAAM,SAAS,OAAO,iBAAiB,WAAW,aAAa,KAAK,IAAI;AACxE,UAAM,SAAS,OAAO,iBAAiB,WAAW,aAAa,KAAK,IAAI;AAGxE,QAAI;AACJ,QAAI;AACH,YAAM,YAAY,MAAM,IAAI,IAAI,CAAC,OAAO,MAAM,aAAa,CAAC;AAC5D,sBAAgB,OAAO,cAAc,WAAW,UAAU,KAAK,IAAI;AAAA,IACpE,QAAQ;AAAA,IAER;AAGA,QAAI;AACJ,QAAI;AACJ,QAAI;AACH,YAAM,eAAe,MAAM,IACzB,UAAU,WAAW,EACrB,MAAM,MAAM,MAAS;AACvB,YAAM,cAAc,MAAM,IACxB,UAAU,YAAY,EACtB,MAAM,MAAM,MAAS;AACvB,eACC,gBAAgB,OAAQ,aAAqB,UAAU,WACnD,aAAqB,MAAM,KAAK,IACjC;AACJ,oBACC,eAAe,OAAQ,YAAoB,UAAU,WACjD,YAAoB,MAAM,KAAK,IAChC;AAAA,IACL,QAAQ;AAAA,IAER;AAGA,QAAI;AACJ,QAAI;AACH,YAAM,YAAY,MAAM,IAAI,IAAI,CAAC,YAAY,UAAU,eAAe,CAAC,EAAE,MAAM,MAAM,MAAS;AAC9F,YAAM,OAAO,cAAc,WAAW,UAAU,KAAK,IAAI;AAAA,IAC1D,QAAQ;AAAA,IAER;AAGA,QAAI;AACJ,UAAM,SAAS,WAAW,QAAQ,SAAS,IAAI,QAAQ,CAAC,IAAI;AAC5D,QAAI,UAAU,OAAO,QAAQ,OAAO,KAAK,OAAO;AAC/C,aAAO,OAAO,KAAK;AAAA,IACpB;AAGA,UAAM,QAAQ,SAAS,OAAO,SAAS,SAAS,KAAK,OAAO,QAAQ,SAAS,KAAK,OAAO,QAAQ,SAAS,IAAI;AAE9G,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD,SAAS,OAAO;AAGf,WAAO,CAAC;AAAA,EACT;AACD;;;ACtGA,qBAAe;AAUR,IAAM,qBAAN,MAAyB;AAAA,EACd;AAAA,EAEjB,YAAY,SAA0B;AACrC,SAAK,UAAU;AAAA,EAChB;AAAA,EAEA,MAAM,OACL,WACA,UACA,aACgB;AAChB,QAAI,KAAK,QAAQ,QAAQ;AACxB,YAAM,QAAQ,eAAAC,QAAG,SAAS,QAAQ;AAClC,cAAQ;AAAA,QACP,oCAAoC,QAAQ,KAAK,MAAM,IAAI;AAAA,MAC5D;AACA;AAAA,IACD;AAEA,UAAM,aAAa,MAAM,eAAAA,QAAG,SAAS,SAAS,QAAQ;AAEtD,UAAM,UAAU,YAAY;AAC3B,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,YAAY;AAAA,QACjB,MAAM,WAAW,MAAM;AAAA,QACvB,KAAK,QAAQ;AAAA,MACd;AAEA,UAAI;AACH,cAAM,WAAW,MAAM,MAAM,WAAW;AAAA,UACvC,QAAQ;AAAA,UACR,SAAS;AAAA,YACR,gBAAgB;AAAA,YAChB,kBAAkB,OAAO,WAAW,MAAM;AAAA,UAC3C;AAAA,UACA,MAAM;AAAA,UACN,QAAQ,WAAW;AAAA,QACpB,CAAC;AAED,YAAI,CAAC,SAAS,IAAI;AACjB,gBAAM,QAAQ,IAAI;AAAA,YACjB,qBAAqB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,UAC5D;AACA,gBAAM,aAAa,SAAS;AAC5B,gBAAM;AAAA,QACP;AAAA,MACD,UAAE;AACD,qBAAa,SAAS;AAAA,MACvB;AAAA,IACD,GAAG,kBAAkB;AAAA,EACtB;AAAA,EAEA,MAAM,YACL,OACA,eAC0B;AAE1B,UAAM,UAAU,MAAM,OAAO,SAAS,GAAG;AACzC,UAAM,QAAQ,OAAO,KAAK,QAAQ,aAAa;AAE/C,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC7B,MAAM;AAAA,QAAI,CAAC,MAAM,UAChB,MAAM,YAAY;AACjB,gBAAM,KAAK,OAAO,KAAK,WAAW,KAAK,UAAU,KAAK,WAAW;AACjE,iBAAO;AAAA,YACN,SAAS;AAAA,YACT,cAAc,cAAc,KAAK,GAAG;AAAA,UACrC;AAAA,QACD,CAAC;AAAA,MACF;AAAA,IACD;AAEA,WAAO,QAAQ,IAAI,CAAC,QAAQ,UAAU;AACrC,UAAI,OAAO,WAAW,aAAa;AAClC,eAAO,OAAO;AAAA,MACf;AACA,aAAO;AAAA,QACN,SAAS;AAAA,QACT,cAAc,cAAc,KAAK,GAAG;AAAA,QACpC,OAAO,OAAO,kBAAkB,QAAQ,OAAO,OAAO,UAAU,OAAO,OAAO,MAAM;AAAA,MACrF;AAAA,IACD,CAAC;AAAA,EACF;AACD;;;AJ5DA,IAAM,kBAAkB;AACxB,IAAM,iCAAiC;AACvC,IAAM,yBAAyB;AAC/B,IAAM,qBAAqB;AAC3B,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AAQzB,IAAqB,6BAArB,MAAoE;AAAA,EAClD;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAA+B,CAAC;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB,oBAAI,IAAsB;AAAA,EAC3C,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAExB,YAAY,UAA2B,CAAC,GAAsB;AAC7D,SAAK,UAAU;AAAA,MACd,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,sBAAsB;AAAA,MACtB,eAAe;AAAA,MACf,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,GAAG;AAAA,IACJ;AAAA,EACD;AAAA,EAEA,MAAM,QAAQ,QAAoB,OAA6B;AAC9D,SAAK,SAAS;AACd,SAAK,UAAU,OAAO;AAGtB,QAAI,CAAC,KAAK,QAAQ,WAAW;AAC5B,WAAK,QAAQ,YAAY,QAAQ,IAAI,uBAAuB;AAAA,IAC7D;AACA,QAAI,CAAC,KAAK,QAAQ,QAAQ;AACzB,WAAK,QAAQ,SAAS,QAAQ,IAAI,oBAAoB;AAAA,IACvD;AAGA,QAAI,QAAQ,IAAI,qBAAqB,QAAQ;AAC5C,WAAK,QAAQ,SAAS;AAAA,IACvB;AAEA,QAAI,CAAC,KAAK,QAAQ,aAAa,CAAC,KAAK,QAAQ,QAAQ;AACpD,UAAI,CAAC,KAAK,QAAQ,QAAQ;AACzB,aAAK;AAAA,UACJ;AAAA,QACD;AACA,aAAK,WAAW;AAChB;AAAA,MACD;AAAA,IACD;AAGA,SAAK,SAAS,IAAI,kBAAkB;AAAA,MACnC,QAAQ,KAAK,QAAQ;AAAA,MACrB,QAAQ,KAAK,QAAQ;AAAA,MACrB,WAAW,KAAK,QAAQ;AAAA,MACxB,eAAe,KAAK,QAAQ;AAAA,MAC5B,QAAQ,KAAK,QAAQ;AAAA,IACtB,CAAC;AAED,SAAK,WAAW,IAAI,mBAAmB;AAAA,MACtC,eAAe,KAAK,QAAQ;AAAA,MAC5B,WAAW,KAAK,QAAQ;AAAA,MACxB,QAAQ,KAAK,QAAQ;AAAA,IACtB,CAAC;AAGD,UAAM,UAAU,MAAM,OAAO,SAAS,GAAG;AACzC,SAAK,cAAc,OAAO,KAAK,QAAQ,oBAAqB;AAE5D,SAAK,aAAY,oBAAI,KAAK,GAAE,YAAY;AACxC,UAAM,WAAW,MAAM,SAAS;AAGhC,UAAM,WAAW,KAAK,oBAAoB,MAAM;AAGhD,UAAM,YAAY,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,EAAE,SAAS,IAAI,CAAC;AAE9D,QAAI;AACH,YAAM,aAA+B;AAAA,QACpC,WAAW,KAAK,QAAQ;AAAA,QACxB,WAAW,KAAK;AAAA,QAChB,YAAY;AAAA,UACX,SAAS,OAAO;AAAA,UAChB,SAAS,OAAO;AAAA,UAChB,SAAS,OAAO,WAAW,CAAC,GAAG,WAAW;AAAA,UAC1C,eAAe,OAAO;AAAA,UACtB,YAAY,OAAO;AAAA,UACnB,eAAe,OAAO;AAAA,UACtB,iBAAiB,OAAO,kBACrB;AAAA,YACA,KAAK,OAAO,gBAAgB;AAAA,YAC5B,WAAW,OAAO,gBAAgB;AAAA,UAClC,IACA;AAAA,QACJ;AAAA,QACA;AAAA,QACA,WAAW;AAAA,UACV,YAAY,UAAU;AAAA,UACtB,YAAY,SAAS;AAAA,UACrB,eAAe,OAAO,UAAU,UAAU;AAAA,QAC3C;AAAA,QACA,OAAO,OAAO,QACX;AAAA,UACA,SAAS,OAAO,MAAM;AAAA,UACtB,OAAO,OAAO,MAAM;AAAA,QACpB,IACA;AAAA,QACH,aAAa,KAAK,mBAAmB,MAAM;AAAA,QAC3C,KAAK,MAAM,KAAK,WAAW;AAAA,QAC3B,YAAY,OAAO;AAAA,QACnB,SAAS,OAAO;AAAA,QAChB,SAAS,OAAO,WAAW,CAAC,GAAG;AAAA,QAC/B,WAAW,OAAO,WAAW,CAAC,GAAG;AAAA,MAClC;AAEA,YAAM,WAAW,MAAM,KAAK,OAAO,UAAU,UAAU;AAEvD,WAAK,QAAQ,SAAS;AACtB,WAAK;AAAA,QACJ,OAAO,KAAK,KAAK,aAAa,SAAS,MAAM,iBAAiB,UAAU,IAAI,WAAW,SAAS,MAAM;AAAA,MACvG;AAAA,IACD,SAAS,OAAO;AACf,WAAK,QAAQ,yBAAyB,KAAK,gBAAgB,KAAK,CAAC,EAAE;AACnE,WAAK,WAAW;AAAA,IACjB;AAAA,EACD;AAAA,EAEA,UAAU,MAAgB,QAA0B;AACnD,QAAI,KAAK,YAAY,CAAC,KAAK,MAAO;AAGlC,QAAI,CAAC,KAAK,sBAAsB,OAAO,WAAW;AACjD,WAAK,qBAAqB,OAAO,UAAU,QAAQ;AAAA,IACpD;AAGA,QACC,CAAC,KAAK,qBACL,OAAO,WAAW,YAAY,OAAO,WAAW,aAChD;AACD,WAAK,mBAAmB,KAAK,IAAI;AAAA,IAClC;AAGA,UAAM,UAAU,KAAK;AAAA,MAAY,MAChC,KAAK,kBAAkB,MAAM,MAAM;AAAA,IACpC;AACA,SAAK,YAAY,KAAK,OAAO;AAAA,EAC9B;AAAA,EAEA,MAAM,MAAM,QAAmC;AAC9C,QAAI,KAAK,YAAY,CAAC,KAAK,OAAO;AACjC,WAAK,QAAQ,mCAAmC;AAChD;AAAA,IACD;AAGA,SAAK,QAAQ,eAAe,KAAK,YAAY,MAAM,qBAAqB;AACxE,UAAM,UAAU,MAAM,QAAQ,WAAW,KAAK,WAAW;AAGzD,UAAM,WAAW,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,UAAU;AAC9D,QAAI,SAAS,SAAS,GAAG;AACxB,WAAK,QAAQ,GAAG,SAAS,MAAM,iBAAiB;AAAA,IACjD;AAGA,UAAM,UAAU,KAAK,eAAe,MAAM;AAC1C,UAAM,WAAU,oBAAI,KAAK,GAAE,YAAY;AACvC,UAAM,YAAY,KAAK,YAAY,IAAI,KAAK,KAAK,SAAS,EAAE,QAAQ,IAAI;AAExE,QAAI;AACH,YAAM,KAAK,OAAO,YAAY,KAAK,OAAO;AAAA,QACzC,QAAQ,KAAK,aAAa,OAAO,MAAM;AAAA,QACvC;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,UACP,iBAAiB,KAAK,MAAM,OAAO,QAAQ;AAAA,UAC3C,iBAAiB,KAAK,qBACnB,KAAK,MAAM,KAAK,qBAAqB,SAAS,IAC9C;AAAA,UACH,oBAAoB,KAAK,mBACtB,KAAK,MAAM,KAAK,mBAAmB,SAAS,IAC5C;AAAA,QACJ;AAAA,MACD,CAAC;AACD,WAAK,QAAQ,OAAO,KAAK,KAAK,eAAe,OAAO,MAAM,EAAE;AAAA,IAC7D,SAAS,OAAO;AACf,WAAK,QAAQ,2BAA2B,KAAK,gBAAgB,KAAK,CAAC,EAAE;AAAA,IACtE;AAAA,EACD;AAAA,EAEA,MAAc,kBACb,MACA,QACgB;AAChB,UAAM,SAAS,KAAK,UAAU,IAAI;AAClC,UAAM,WAAW,KAAK,YAAY,QAAQ,OAAO,KAAK;AAEtD,QAAI;AACH,YAAM,UAAU,KAAK,iBAAiB,MAAM,MAAM;AAClD,YAAM,KAAK,OAAO,WAAW,KAAK,OAAQ,OAAO;AAGjD,UAAI,KAAK,QAAQ,cAAc;AAC9B,cAAM,KAAK,kBAAkB,QAAQ,QAAQ;AAAA,MAC9C;AAGA,YAAM,UAAU,KAAK,eAAe,MAAM,MAAM;AAChD,YAAM,WAAW,KAAK,eAAe,IAAI,MAAM;AAC/C,WAAK,eAAe,IAAI,QAAQ;AAAA,QAC/B,QAAQ,OAAO;AAAA,QACf;AAAA,QACA,UAAU,UAAU,WAAW,MAAM,OAAO,QAAQ,IAAI,IAAI;AAAA,MAC7D,CAAC;AAAA,IACF,SAAS,OAAO;AACf,WAAK;AAAA,QACJ,yBAAyB,KAAK,KAAK,KAAK,KAAK,gBAAgB,KAAK,CAAC;AAAA,MACpE;AACA,YAAM;AAAA,IACP;AAAA,EACD;AAAA,EAEQ,iBACP,MACA,QACoB;AACpB,UAAM,SAAS,KAAK,UAAU,IAAI;AAGlC,UAAM,OAAO,KAAK,QAAQ,CAAC;AAG3B,UAAM,cAAc,KAAK,qBAAqB,KAAK,eAAe,CAAC,CAAC;AAGpE,UAAM,cAA+B;AAAA,MACpC,UAAU,KAAK,YAAY,QAAQ,OAAO,KAAK;AAAA,MAC/C,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,WAAW,OAAO,WAAW,cAAc,KAAK;AAAA,MAChD,YAAY,OAAO;AAAA,MACnB,aAAa,OAAO;AAAA,MACpB,eAAe,OAAO;AAAA,MACtB,QAAQ,KAAK,gBAAgB,OAAO,UAAU,CAAC,CAAC;AAAA,MAChD,OAAO,KAAK,eAAe,OAAO,SAAS,CAAC,CAAC;AAAA,MAC7C,aAAa,KAAK,qBAAqB,OAAO,eAAe,CAAC,CAAC;AAAA,MAC/D,aAAa,KAAK,wBAAwB,OAAO,eAAe,CAAC,CAAC;AAAA,MAClE,QAAQ,KAAK,gBAAgB,OAAO,UAAU,CAAC,GAAG,gBAAgB;AAAA,MAClE,QAAQ,KAAK,gBAAgB,OAAO,UAAU,CAAC,GAAG,gBAAgB;AAAA,IACnE;AAGA,UAAM,cAAc,KAAK,QAAQ,QAAQ,GAAG,QAAQ;AAEpD,WAAO;AAAA,MACN;AAAA,MACA,cAAc,KAAK;AAAA,MACnB,MAAM,KAAK,aAAa,KAAK,SAAS,IAAI;AAAA,MAC1C,UAAU;AAAA,QACT,MAAM,KAAK,aAAa,KAAK,SAAS,IAAI;AAAA,QAC1C,MAAM,KAAK,SAAS;AAAA,QACpB,QAAQ,KAAK,SAAS;AAAA,MACvB;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK,UAAU;AAAA,MAC1B;AAAA,MACA;AAAA,MACA,gBAAgB,KAAK;AAAA,MACrB,SAAS,KAAK,eAAe,MAAM,MAAM;AAAA,MACzC,SAAS,KAAK;AAAA,MACd,SAAS,KAAK;AAAA,MACd,iBACC,KAAK,kBAAkB,IAAI,KAAK,kBAAkB;AAAA,MACnD,QAAQ,OAAO;AAAA,MACf,YAAY,OAAO;AAAA,MACnB,YAAY,OAAO;AAAA,MACnB,SAAS,CAAC,WAAW;AAAA,MACrB;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,eAAe,OAAwC;AAC9D,WAAO,MAAM,IAAI,CAAC,SAAS,KAAK,cAAc,IAAI,CAAC;AAAA,EACpD;AAAA,EAEQ,cAAc,MAA0B;AAC/C,UAAM,SAAS,QAAQ,EAAE,KAAK,aAAa;AAE3C,WAAO;AAAA,MACN;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,UAAU,KAAK;AAAA,MACf,WAAW,KAAK,WAAW,cAAc,KAAK;AAAA,MAC9C,YAAY,KAAK;AAAA,MACjB,UAAU,KAAK,WACZ;AAAA,QACA,MAAM,KAAK,aAAa,KAAK,SAAS,IAAI;AAAA,QAC1C,MAAM,KAAK,SAAS;AAAA,QACpB,QAAQ,KAAK,SAAS;AAAA,MACtB,IACA;AAAA,MACH,OAAO,KAAK,QAAQ,KAAK,eAAe,KAAK,KAAK,IAAI;AAAA;AAAA,MAEtD,OACC,KAAK,SAAS,KAAK,MAAM,SAAS,IAC/B,KAAK,eAAe,KAAK,KAAK,IAC9B;AAAA;AAAA,MAEJ,aAAa;AAAA;AAAA,IACd;AAAA,EACD;AAAA,EAEQ,gBACP,QACc;AACd,WAAO,OAAO,IAAI,CAAC,MAAM,KAAK,eAAe,CAAC,CAAC;AAAA,EAChD;AAAA,EAEQ,eAAe,OAKT;AACb,WAAO;AAAA,MACN,SAAS,MAAM;AAAA,MACf,OAAO,MAAM;AAAA,MACb,OAAO,MAAM,UAAU,SAAY,OAAO,MAAM,KAAK,IAAI;AAAA,MACzD,UAAU,MAAM,WACb;AAAA,QACA,MAAM,KAAK,aAAa,MAAM,SAAS,IAAI;AAAA,QAC3C,MAAM,MAAM,SAAS;AAAA,QACrB,QAAQ,MAAM,SAAS;AAAA,MACvB,IACA;AAAA;AAAA,MAEH,SAAS;AAAA,IACV;AAAA,EACD;AAAA,EAEQ,qBACP,aACmB;AACnB,WAAO,YAAY,IAAI,CAAC,OAAO;AAAA,MAC9B,MAAM,EAAE;AAAA,MACR,aAAa,EAAE;AAAA,IAChB,EAAE;AAAA,EACH;AAAA,EAEQ,wBACP,aACmB;AACnB,WAAO,YACL,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAC9B,IAAI,CAAC,OAAO;AAAA,MACZ,MAAM,EAAE;AAAA,MACR,UAAU,EAAE,OAAO,iBAAAC,QAAK,SAAS,EAAE,IAAI,IAAI,EAAE;AAAA,MAC7C,aAAa,EAAE;AAAA,MACf,WAAW,KAAK,kBAAkB,CAAC;AAAA,MACnC,MAAM,KAAK,kBAAkB,EAAE,MAAM,EAAE,WAAW;AAAA,IACnD,EAAE;AAAA,EACJ;AAAA,EAEQ,gBACP,QACA,UACW;AACX,UAAM,QAAkB,CAAC;AACzB,eAAW,SAAS,QAAQ;AAC3B,YAAM,MAAM,MAAM,SAAS;AAC3B,YAAM,KAAK,GAAG,IAAI,MAAM,IAAI,CAAC;AAC7B,UAAI,MAAM,UAAU,SAAU;AAAA,IAC/B;AACA,WAAO,MAAM,MAAM,GAAG,QAAQ;AAAA,EAC/B;AAAA,EAEQ,eAAe,MAAgB,QAAiC;AAEvE,QAAI,OAAO,KAAK,YAAY,YAAY;AACvC,aAAO,KAAK,QAAQ;AAAA,IACrB;AAGA,QAAI,OAAO,WAAW,UAAW,QAAO;AACxC,QAAI,OAAO,WAAW,KAAK,eAAgB,QAAO;AAClD,QAAI,OAAO,WAAW,YAAY,OAAO,QAAQ,EAAG,QAAO;AAC3D,WAAO;AAAA,EACR;AAAA,EAEA,MAAc,kBACb,QACA,cACgB;AAChB,UAAM,eAAe,OAAO,eAAe,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI;AACnE,QAAI,YAAY,WAAW,EAAG;AAE9B,UAAM,OAAyB,YAAY,IAAI,CAAC,OAAO;AAAA,MACtD,MAAM,EAAE;AAAA,MACR,UAAU,iBAAAA,QAAK,SAAS,EAAE,IAAK;AAAA,MAC/B,aAAa,EAAE;AAAA,MACf,WAAW,KAAK,YAAY,EAAE,IAAK;AAAA,MACnC,MAAM,KAAK,kBAAkB,EAAE,MAAM,EAAE,WAAW;AAAA,IACnD,EAAE;AAEF,QAAI;AACH,YAAM,EAAE,QAAQ,IAAI,MAAM,KAAK,OAAO,gBAAgB,KAAK,OAAQ;AAAA,QAClE;AAAA,QACA,aAAa;AAAA,MACd,CAAC;AAED,YAAM,cAAc,QAAQ,IAAI,CAAC,GAAG,OAAO;AAAA,QAC1C,WAAW,EAAE;AAAA,QACb,UAAU,YAAY,CAAC,EAAE;AAAA,QACzB,aAAa,YAAY,CAAC,EAAE;AAAA,MAC7B,EAAE;AAEF,YAAM,UAAU,MAAM,KAAK,SAAS,YAAY,aAAa,OAAO;AACpE,YAAM,cAAc,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE;AAEtD,UAAI,cAAc,GAAG;AACpB,aAAK,QAAQ,GAAG,WAAW,4BAA4B;AAAA,MACxD;AAAA,IACD,SAAS,OAAO;AACf,WAAK;AAAA,QACJ,iCAAiC,KAAK,gBAAgB,KAAK,CAAC;AAAA,MAC7D;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,oBAAoB,QAAqC;AAChE,YAAQ,OAAO,YAAY,CAAC,GAAG,IAAI,CAAC,YAAY;AAC/C,YAAM,MAAM,QAAQ,OAAO,CAAC;AAC5B,aAAO;AAAA,QACN,MAAM,QAAQ;AAAA,QACd,aAAa,IAAI;AAAA,QACjB,SAAS,IAAI;AAAA,QACb,YAAa,IAAgC;AAAA,QAC7C,UAAU,IAAI,WACX,EAAE,OAAQ,IAAI,SAA+C,OAAO,QAAS,IAAI,SAA+C,OAAO,IACvI;AAAA,QACH,QAAQ,IAAI;AAAA,QACZ,UAAU,IAAI;AAAA,QACd,aAAa,IAAI;AAAA,QACjB,UAAU,IAAI;AAAA,QACd,UAAU,IAAI;AAAA,QACd,SAAS,QAAQ;AAAA,QACjB,SAAS,QAAQ;AAAA,MAClB;AAAA,IACD,CAAC;AAAA,EACF;AAAA,EAEQ,mBAAmB,QAAqC;AAC/D,UAAM,SAAS,KAAK,UAAU;AAE9B,WAAO;AAAA,MACN,IAAI;AAAA,QACH,UAAU,eAAAC,QAAG,SAAS;AAAA,QACtB,SAAS,eAAAA,QAAG,QAAQ;AAAA,QACpB,MAAM,eAAAA,QAAG,KAAK;AAAA,MACf;AAAA,MACA,MAAM;AAAA,QACL,SAAS,QAAQ;AAAA,MAClB;AAAA,MACA,SAAS;AAAA,QACR,MAAM,eAAAA,QAAG,KAAK,EAAE;AAAA,QAChB,QAAQ,eAAAA,QAAG,SAAS;AAAA,QACpB,UAAU,eAAAA,QAAG,SAAS;AAAA,MACvB;AAAA,MACA,YAAY;AAAA,QACX,SAAS,OAAO;AAAA,MACjB;AAAA,MACA,IAAI;AAAA,IACL;AAAA,EACD;AAAA,EAEQ,eAAe,QAAgC;AACtD,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,QAAQ;AACZ,QAAI,UAAU;AACd,QAAI,WAAW;AACf,QAAI,cAAc;AAClB,QAAI,WAAW;AACf,QAAI,aAAa;AAEjB,eAAW,CAAC,EAAE,QAAQ,KAAK,KAAK,gBAAgB;AAC/C,cAAQ,SAAS,QAAQ;AAAA,QACxB,KAAK;AACJ,oBAAU;AACV,cAAI,SAAS,UAAU,EAAG,UAAS;AACnC;AAAA,QACD,KAAK;AACJ,oBAAU;AACV;AAAA,QACD,KAAK;AACJ,qBAAW;AACX;AAAA,QACD,KAAK;AACJ,sBAAY;AACZ;AAAA,QACD,KAAK;AACJ,yBAAe;AACf;AAAA,MACF;AAGA,UAAI,SAAS,YAAY,WAAY,aAAY;AAAA,eACxC,SAAS,YAAY,aAAc,eAAc;AAAA,IAC3D;AAEA,WAAO;AAAA,MACN,OAAO,KAAK,eAAe;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,KAAK,MAAM,OAAO,QAAQ;AAAA,MACtC;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,aACP,QACyC;AACzC,YAAQ,QAAQ;AAAA,MACf,KAAK;AACJ,eAAO;AAAA,MACR,KAAK;AAAA,MACL,KAAK;AACJ,eAAO;AAAA,MACR,KAAK;AACJ,eAAO;AAAA,MACR;AACC,eAAO;AAAA,IACT;AAAA,EACD;AAAA,EAEQ,UAAU,MAAwB;AACzC,UAAM,MAAM,GAAG,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,KAAK;AACrE,WAAO,KAAK,QAAQ,GAAG;AAAA,EACxB;AAAA,EAEQ,YAAY,QAAgB,OAAuB;AAC1D,WAAO,KAAK,QAAQ,GAAG,MAAM,IAAI,KAAK,EAAE;AAAA,EACzC;AAAA,EAEQ,QAAQ,OAAuB;AACtC,eAAO,+BAAW,QAAQ,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAAA,EACpE;AAAA,EAEQ,aAAa,UAA0B;AAC9C,QAAI,KAAK,WAAW,SAAS,WAAW,KAAK,OAAO,GAAG;AACtD,aAAO,SAAS,MAAM,KAAK,QAAQ,SAAS,CAAC;AAAA,IAC9C;AACA,WAAO;AAAA,EACR;AAAA,EAEQ,YAAY,UAA0B;AAC7C,QAAI;AACH,aAAO,gBAAAC,QAAG,SAAS,QAAQ,EAAE;AAAA,IAC9B,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEQ,kBACP,YACS;AACT,QAAI,WAAW,MAAM;AACpB,aAAO,KAAK,YAAY,WAAW,IAAI;AAAA,IACxC;AACA,QAAI,WAAW,MAAM;AACpB,aAAO,WAAW,KAAK;AAAA,IACxB;AACA,WAAO;AAAA,EACR;AAAA,EAEQ,kBAAkB,MAAc,aAAqC;AAC5E,QAAI,SAAS,WAAW,YAAY,WAAW,QAAQ,EAAG,QAAO;AACjE,QAAI,SAAS,WAAW,KAAK,SAAS,MAAM,EAAG,QAAO;AACtD,QAAI,KAAK,SAAS,YAAY,KAAK,gBAAgB;AAClD,aAAO;AACR,QAAI,SAAS,SAAU,QAAO;AAC9B,QAAI,SAAS,SAAU,QAAO;AAC9B,WAAO;AAAA,EACR;AAAA,EAEA,MAAc,aAAa;AAE1B,UAAM,YAAY;AAAA,MACjB,QACC,QAAQ,IAAI,mBACZ,QAAQ,IAAI,mBACZ,QAAQ,IAAI,oBACZ,QAAQ,IAAI,2BACZ,QAAQ,IAAI;AAAA,MACb,QACC,QAAQ,IAAI,cACZ,QAAQ,IAAI,iBACZ,QAAQ,IAAI,wBACZ,QAAQ,IAAI;AAAA,MACb,eACC,QAAQ,IAAI,qBACZ,QAAQ,IAAI;AAAA,MACb,MACC,QAAQ,IAAI,qBACZ,QAAQ,IAAI,mBACZ,QAAQ,IAAI,0BACZ,QAAQ,IAAI;AAAA,MACb,QAAQ,QAAQ,IAAI,gBAAgB,QAAQ,IAAI;AAAA,MAChD,aAAa,QAAQ,IAAI;AAAA,MACzB,KAAK,QAAQ,IAAI,oBAAoB,QAAQ,QAAQ,IAAI,kBAAkB;AAAA,IAC5E;AAGA,QAAI,UAAU,UAAU,UAAU,QAAQ;AACzC,aAAO;AAAA,IACR;AAGA,UAAM,eAAe,MAAM,gBAAgB,KAAK,OAAO;AAGvD,WAAO;AAAA,MACN,QAAQ,UAAU,UAAU,aAAa;AAAA,MACzC,QAAQ,UAAU,UAAU,aAAa;AAAA,MACzC,eAAe,UAAU,iBAAiB,aAAa;AAAA,MACvD,MAAM,UAAU,QAAQ,aAAa;AAAA,MACrC,QAAQ,UAAU,UAAU,aAAa;AAAA,MACzC,aAAa,UAAU,eAAe,aAAa;AAAA,MACnD,KAAK,UAAU,OAAO,aAAa;AAAA,MACnC,OAAO,aAAa;AAAA,IACrB;AAAA,EACD;AAAA,EAEQ,YAA+C;AACtD,QAAI,QAAQ,IAAI,gBAAgB;AAC/B,aAAO;AAAA,QACN,UAAU;AAAA,QACV,OAAO,QAAQ,IAAI;AAAA,QACnB,OAAO,QAAQ,IAAI;AAAA,QACnB,QAAQ,GAAG,QAAQ,IAAI,iBAAiB,IAAI,QAAQ,IAAI,iBAAiB,iBAAiB,QAAQ,IAAI,aAAa;AAAA,QACnH,aAAa,QAAQ,IAAI;AAAA,QACzB,QAAQ,QAAQ,IAAI;AAAA,QACpB,aAAa,QAAQ,IAAI,sBAAsB,iBAC5C;AAAA,UACA,QAAQ,QAAQ,IAAI,oBAAoB;AAAA,UACxC,KAAK,GAAG,QAAQ,IAAI,iBAAiB,IAAI,QAAQ,IAAI,iBAAiB,SAAS,QAAQ,IAAI,gBAAgB;AAAA,QAC3G,IACA;AAAA,MACJ;AAAA,IACD;AACA,QAAI,QAAQ,IAAI,WAAW;AAC1B,aAAO;AAAA,QACN,UAAU;AAAA,QACV,OAAO,QAAQ,IAAI;AAAA,QACnB,OAAO,QAAQ,IAAI;AAAA,QACnB,QAAQ,QAAQ,IAAI;AAAA,QACpB,aAAa,QAAQ,IAAI;AAAA,QACzB,QAAQ,QAAQ,IAAI;AAAA,QACpB,aAAa,QAAQ,IAAI,uBACtB;AAAA,UACA,QAAQ,QAAQ,IAAI;AAAA,UACpB,KAAK,QAAQ,IAAI,+BAA+B,uBAAuB,QAAQ,IAAI;AAAA,UACnF,OAAO,QAAQ,IAAI;AAAA,QACnB,IACA;AAAA,MACJ;AAAA,IACD;AACA,QAAI,QAAQ,IAAI,aAAa;AAC5B,aAAO;AAAA,QACN,UAAU;AAAA,QACV,OAAO,QAAQ,IAAI;AAAA,QACnB,QAAQ,QAAQ,IAAI;AAAA,QACpB,aAAa,QAAQ,IAAI;AAAA,QACzB,QAAQ,QAAQ,IAAI;AAAA,MACrB;AAAA,IACD;AACA,QAAI,QAAQ,IAAI,UAAU;AACzB,aAAO;AAAA,QACN,UAAU;AAAA,QACV,OAAO,QAAQ,IAAI;AAAA,QACnB,OAAO,QAAQ,IAAI;AAAA,QACnB,QAAQ,QAAQ,IAAI;AAAA,QACpB,aAAa,QAAQ,IAAI;AAAA,QACzB,QAAQ,QAAQ,IAAI;AAAA,QACpB,aAAa,QAAQ,IAAI,sBACtB;AAAA,UACA,QAAQ,QAAQ,IAAI,oBAAoB;AAAA,UACxC,KAAK,QAAQ,IAAI;AAAA,QACjB,IACA;AAAA,MACJ;AAAA,IACD;AACA,QAAI,QAAQ,IAAI,QAAQ;AACvB,aAAO;AAAA,QACN,UAAU;AAAA,QACV,OAAO,QAAQ,IAAI;AAAA,QACnB,OAAO,QAAQ,IAAI;AAAA,QACnB,QAAQ,QAAQ,IAAI;AAAA,QACpB,aAAa,QAAQ,IAAI;AAAA,QACzB,QAAQ,QAAQ,IAAI;AAAA,QACpB,aAAa,QAAQ,IAAI,wBAAwB,UAC9C;AAAA,UACA,QAAQ,QAAQ,IAAI,uBAAuB;AAAA,UAC3C,KAAK,sBAAsB,QAAQ,IAAI,gBAAgB,SAAS,QAAQ,IAAI,mBAAmB;AAAA,QAC/F,IACA;AAAA,MACJ;AAAA,IACD;AACA,QAAI,QAAQ,IAAI,WAAW;AAC1B,aAAO;AAAA,QACN,UAAU;AAAA,QACV,OAAO,QAAQ,IAAI;AAAA,QACnB,OAAO,QAAQ,IAAI;AAAA,QACnB,QAAQ,QAAQ,IAAI;AAAA,QACpB,aAAa,QAAQ,IAAI;AAAA,QACzB,QAAQ,QAAQ,IAAI;AAAA,MACrB;AAAA,IACD;AACA,QAAI,QAAQ,IAAI,mBAAmB,QAAQ,IAAI,UAAU;AACxD,aAAO;AAAA,QACN,UAAU;AAAA,QACV,OAAO,QAAQ,IAAI;AAAA,QACnB,QAAQ,GAAG,QAAQ,IAAI,oBAAoB,GAAG,QAAQ,IAAI,kBAAkB,2BAA2B,QAAQ,IAAI,aAAa;AAAA,QAChI,aAAa,QAAQ,IAAI;AAAA,QACzB,QAAQ,QAAQ,IAAI;AAAA,MACrB;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA,EAEQ,gBAAgB,OAAwB;AAC/C,QAAI,iBAAiB,MAAO,QAAO,MAAM;AACzC,WAAO,OAAO,KAAK;AAAA,EACpB;AAAA,EAEQ,QAAQ,SAAuB;AACtC,YAAQ,IAAI,cAAc,OAAO,EAAE;AAAA,EACpC;AAAA,EAEQ,QAAQ,SAAuB;AACtC,YAAQ,KAAK,cAAc,OAAO,EAAE;AAAA,EACrC;AACD;","names":["import_node_fs","path","fs","path","os","fs"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
|
+
|
|
3
|
+
type ReporterOptions = {
|
|
4
|
+
projectId: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
apiUrl?: string;
|
|
7
|
+
uploadAssets?: boolean;
|
|
8
|
+
maxConcurrentUploads?: number;
|
|
9
|
+
retryAttempts?: number;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
declare class SupatestPlaywrightReporter implements Reporter {
|
|
15
|
+
private readonly options;
|
|
16
|
+
private client;
|
|
17
|
+
private uploader;
|
|
18
|
+
private runId?;
|
|
19
|
+
private uploadQueue;
|
|
20
|
+
private uploadLimit;
|
|
21
|
+
private startedAt?;
|
|
22
|
+
private firstTestStartTime?;
|
|
23
|
+
private firstFailureTime?;
|
|
24
|
+
private testsProcessed;
|
|
25
|
+
private disabled;
|
|
26
|
+
private config?;
|
|
27
|
+
private rootDir?;
|
|
28
|
+
private stepIdCounter;
|
|
29
|
+
constructor(options?: ReporterOptions);
|
|
30
|
+
onBegin(config: FullConfig, suite: Suite): Promise<void>;
|
|
31
|
+
onTestEnd(test: TestCase, result: TestResult): void;
|
|
32
|
+
onEnd(result: FullResult): Promise<void>;
|
|
33
|
+
private processTestResult;
|
|
34
|
+
private buildTestPayload;
|
|
35
|
+
private serializeSteps;
|
|
36
|
+
private serializeStep;
|
|
37
|
+
private serializeErrors;
|
|
38
|
+
private serializeError;
|
|
39
|
+
private serializeAnnotations;
|
|
40
|
+
private serializeAttachmentMeta;
|
|
41
|
+
private serializeOutput;
|
|
42
|
+
private getTestOutcome;
|
|
43
|
+
private uploadAttachments;
|
|
44
|
+
private buildProjectConfigs;
|
|
45
|
+
private getEnvironmentInfo;
|
|
46
|
+
private computeSummary;
|
|
47
|
+
private mapRunStatus;
|
|
48
|
+
private getTestId;
|
|
49
|
+
private getResultId;
|
|
50
|
+
private hashKey;
|
|
51
|
+
private relativePath;
|
|
52
|
+
private getFileSize;
|
|
53
|
+
private getAttachmentSize;
|
|
54
|
+
private getAttachmentKind;
|
|
55
|
+
private getGitInfo;
|
|
56
|
+
private getCIInfo;
|
|
57
|
+
private getErrorMessage;
|
|
58
|
+
private logInfo;
|
|
59
|
+
private logWarn;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { type ReporterOptions, SupatestPlaywrightReporter as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
|
+
|
|
3
|
+
type ReporterOptions = {
|
|
4
|
+
projectId: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
apiUrl?: string;
|
|
7
|
+
uploadAssets?: boolean;
|
|
8
|
+
maxConcurrentUploads?: number;
|
|
9
|
+
retryAttempts?: number;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
declare class SupatestPlaywrightReporter implements Reporter {
|
|
15
|
+
private readonly options;
|
|
16
|
+
private client;
|
|
17
|
+
private uploader;
|
|
18
|
+
private runId?;
|
|
19
|
+
private uploadQueue;
|
|
20
|
+
private uploadLimit;
|
|
21
|
+
private startedAt?;
|
|
22
|
+
private firstTestStartTime?;
|
|
23
|
+
private firstFailureTime?;
|
|
24
|
+
private testsProcessed;
|
|
25
|
+
private disabled;
|
|
26
|
+
private config?;
|
|
27
|
+
private rootDir?;
|
|
28
|
+
private stepIdCounter;
|
|
29
|
+
constructor(options?: ReporterOptions);
|
|
30
|
+
onBegin(config: FullConfig, suite: Suite): Promise<void>;
|
|
31
|
+
onTestEnd(test: TestCase, result: TestResult): void;
|
|
32
|
+
onEnd(result: FullResult): Promise<void>;
|
|
33
|
+
private processTestResult;
|
|
34
|
+
private buildTestPayload;
|
|
35
|
+
private serializeSteps;
|
|
36
|
+
private serializeStep;
|
|
37
|
+
private serializeErrors;
|
|
38
|
+
private serializeError;
|
|
39
|
+
private serializeAnnotations;
|
|
40
|
+
private serializeAttachmentMeta;
|
|
41
|
+
private serializeOutput;
|
|
42
|
+
private getTestOutcome;
|
|
43
|
+
private uploadAttachments;
|
|
44
|
+
private buildProjectConfigs;
|
|
45
|
+
private getEnvironmentInfo;
|
|
46
|
+
private computeSummary;
|
|
47
|
+
private mapRunStatus;
|
|
48
|
+
private getTestId;
|
|
49
|
+
private getResultId;
|
|
50
|
+
private hashKey;
|
|
51
|
+
private relativePath;
|
|
52
|
+
private getFileSize;
|
|
53
|
+
private getAttachmentSize;
|
|
54
|
+
private getAttachmentKind;
|
|
55
|
+
private getGitInfo;
|
|
56
|
+
private getCIInfo;
|
|
57
|
+
private getErrorMessage;
|
|
58
|
+
private logInfo;
|
|
59
|
+
private logWarn;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { type ReporterOptions, SupatestPlaywrightReporter as default };
|