From a45247ff3e015cb433dd4f864186b54662f92fe4 Mon Sep 17 00:00:00 2001 From: Matthew Thomas Date: Sun, 7 Jun 2026 10:03:56 +0100 Subject: [PATCH] chore: migrate linting and formatting to biome --- .prettierignore | 11 - .prettierrc | 10 - biome.json | 43 + eslint.config.mjs | 35 - examples/comprehensive.json | 496 +-- examples/minimal.json | 48 +- examples/with-diagnostics.json | 108 +- examples/with-insights.json | 152 +- examples/with-retries.json | 104 +- package-lock.json | 5225 +++++++++++++------------------- package.json | 136 +- src/add-insights.test.ts | 466 +-- src/add-insights.ts | 246 +- src/cli.ts | 474 +-- src/exit-codes.ts | 10 +- src/filter.test.ts | 725 +++-- src/filter.ts | 290 +- src/flaky.test.ts | 298 +- src/flaky.ts | 48 +- src/generate-report-id.test.ts | 320 +- src/generate-report-id.ts | 100 +- src/generate-test-ids.test.ts | 308 +- src/generate-test-ids.ts | 118 +- src/merge.test.ts | 468 +-- src/merge.ts | 254 +- src/validate.test.ts | 383 ++- src/validate.ts | 142 +- tsconfig.json | 43 +- tsconfig.test.json | 18 +- vitest.config.mts | 46 +- 30 files changed, 5000 insertions(+), 6125 deletions(-) delete mode 100644 .prettierignore delete mode 100644 .prettierrc create mode 100644 biome.json delete mode 100644 eslint.config.mjs diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 24d0160..0000000 --- a/.prettierignore +++ /dev/null @@ -1,11 +0,0 @@ -dist/ -node_modules/ -coverage/ -templates/ -reports/ -community-reports/ -.github/ -.exlintrc.js -README.md -docs/ -CODE_OF_CONDUCT.md diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index e505faf..0000000 --- a/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "semi": false, - "singleQuote": true, - "trailingComma": "es5", - "tabWidth": 2, - "useTabs": false, - "printWidth": 80, - "bracketSpacing": true, - "arrowParens": "avoid" -} diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..4261efe --- /dev/null +++ b/biome.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "includes": [ + "**", + "!dist", + "!coverage", + "!reports", + "!community-reports", + "!templates", + "!.github", + "!docs" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "off" + } + } + } +} diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index 2ebe1b2..0000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,35 +0,0 @@ -// @ts-check -import js from '@eslint/js' -import typescript from 'typescript-eslint' - -export default [ - { - ignores: [ - 'node_modules/**', - 'dist/**', - 'coverage/**', - 'docs/**', - 'ctrf/**', - '*.js', - '*.mjs', - 'src/test-utils/**', - 'scripts/**', - 'examples/**', - 'final/**', - 'src/__tests__/**', - 'src/test-utils/**', - 'src/test-utils/**', - '**/*.test.ts', - 'src/cli.ts', - ], - }, - js.configs.recommended, - ...typescript.configs.recommended, - { - files: ['**/*.ts'], - rules: { - '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-unused-vars': 'error', - }, - }, -] diff --git a/examples/comprehensive.json b/examples/comprehensive.json index 3c8b430..51b300f 100644 --- a/examples/comprehensive.json +++ b/examples/comprehensive.json @@ -1,250 +1,250 @@ { - "reportFormat": "CTRF", - "specVersion": "1.0.0", - "reportId": "9d2c6a10-3f7a-4e22-9a8f-1a2b3c4d5e6f", - "timestamp": "2025-11-24T12:00:00Z", - "generatedBy": "example-ci", - "extra": { - "pipelineStage": "e2e", - "trigger": "pull_request" - }, - "results": { - "tool": { - "name": "example-runner", - "version": "3.5.0", - "extra": { - "plugins": ["retry", "screenshots", "video"] - } - }, - "summary": { - "tests": 3, - "passed": 2, - "failed": 1, - "pending": 0, - "skipped": 0, - "other": 0, - "flaky": 1, - "suites": 2, - "start": 1609459200000, - "stop": 1609459220000, - "duration": 20000 - }, - "tests": [ - { - "id": "550e8400-e29b-41d4-a716-446655440000", - "name": "user can login", - "status": "passed", - "duration": 3500, - "start": 1609459200000, - "stop": 1609459203500, - "suite": ["Authentication", "Login"], - "type": "e2e", - "tags": ["smoke", "critical"], - "filePath": "tests/auth/login.test.js", - "browser": "chromium", - "threadId": "worker-1", - "insights": { - "passRate": { - "current": 1.0, - "baseline": 0.95, - "change": 0.05 - }, - "averageTestDuration": { - "current": 3500, - "baseline": 3200, - "change": 300 - }, - "executedInRuns": 15 - } - }, - { - "id": "550e8400-e29b-41d4-a716-446655440001", - "name": "user can checkout", - "status": "passed", - "duration": 5200, - "start": 1609459203500, - "stop": 1609459208700, - "suite": ["Checkout"], - "type": "e2e", - "tags": ["smoke"], - "filePath": "tests/checkout/checkout.test.js", - "browser": "chromium", - "threadId": "worker-2", - "flaky": true, - "retries": 1, - "retryAttempts": [ - { - "attempt": 1, - "status": "failed", - "duration": 5100, - "start": 1609459203500, - "stop": 1609459208600, - "message": "Payment gateway timeout", - "trace": "Error: Payment gateway timeout\n at processPayment (payment.js:23:10)", - "attachments": [ - { - "name": "attempt-1-screenshot.png", - "contentType": "image/png", - "path": "/artifacts/retry-1.png" - } - ] - }, - { - "attempt": 2, - "status": "passed", - "duration": 5200, - "start": 1609459208700, - "stop": 1609459213900 - } - ], - "insights": { - "passRate": { - "current": 1.0, - "baseline": 0.9, - "change": 0.1 - }, - "flakyRate": { - "current": 0.2, - "baseline": 0.3, - "change": -0.1 - }, - "executedInRuns": 15 - } - }, - { - "id": "550e8400-e29b-41d4-a716-446655440002", - "name": "admin dashboard loads", - "status": "failed", - "duration": 8000, - "start": 1609459213900, - "stop": 1609459221900, - "suite": ["Admin", "Dashboard"], - "type": "e2e", - "tags": ["regression"], - "filePath": "tests/admin/dashboard.test.js", - "line": 42, - "browser": "chromium", - "threadId": "worker-1", - "message": "Element not found: .dashboard-metrics", - "trace": "Error: Element not found: .dashboard-metrics\n at waitForElement (test.js:45:10)\n at Object. (dashboard.test.js:42:5)", - "snippet": "const metrics = await page.locator('.dashboard-metrics');\nawait expect(metrics).toBeVisible();", - "screenshot": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", - "stdout": ["Loading admin dashboard", "Checking authentication"], - "stderr": [ - "Warning: Slow network detected", - "Error: Timeout waiting for .dashboard-metrics" - ], - "attachments": [ - { - "name": "failure-screenshot.png", - "contentType": "image/png", - "path": "/artifacts/screenshots/admin-dashboard-fail.png" - }, - { - "name": "trace.zip", - "contentType": "application/zip", - "path": "/artifacts/traces/admin-dashboard.trace.zip" - }, - { - "name": "video.webm", - "contentType": "video/webm", - "path": "/artifacts/videos/admin-dashboard.webm" - } - ], - "parameters": { - "userRole": "admin", - "environment": "staging" - }, - "steps": [ - { - "name": "Navigate to dashboard", - "status": "passed" - }, - { - "name": "Wait for metrics", - "status": "failed" - } - ], - "insights": { - "passRate": { - "current": 0.0, - "baseline": 0.8, - "change": -0.8 - }, - "executedInRuns": 15 - } - } - ], - "environment": { - "reportName": "PR #123 E2E Tests", - "appName": "example-app", - "appVersion": "2.5.0", - "buildId": "build-456", - "buildName": "PR #123", - "buildNumber": 456, - "buildUrl": "https://ci.example.com/builds/456", - "repositoryName": "example-app", - "repositoryUrl": "https://github.com/example/example-app", - "commit": "a1b2c3d4e5f6", - "branchName": "feature/new-dashboard", - "osPlatform": "linux", - "osRelease": "5.10.0", - "osVersion": "Ubuntu 20.04", - "testEnvironment": "staging", - "healthy": false, - "extra": { - "nodeVersion": "18.0.0", - "region": "us-west-2" - } - }, - "extra": { - "shardIndex": 1, - "totalShards": 4 - } - }, - "insights": { - "passRate": { - "current": 0.67, - "baseline": 0.85, - "change": -0.18 - }, - "failRate": { - "current": 0.33, - "baseline": 0.15, - "change": 0.18 - }, - "flakyRate": { - "current": 0.33, - "baseline": 0.2, - "change": 0.13 - }, - "averageRunDuration": { - "current": 20000, - "baseline": 18000, - "change": 2000 - }, - "p95RunDuration": { - "current": 22000, - "baseline": 20000, - "change": 2000 - }, - "averageTestDuration": { - "current": 5567, - "baseline": 5100, - "change": 467 - }, - "runsAnalyzed": 10 - }, - "baseline": { - "reportId": "2f8c9a90-3e1a-4c3d-9b3a-8f0a9c123456", - "timestamp": "2025-11-23T12:00:00Z", - "source": "main", - "buildNumber": 442, - "buildName": "Nightly Build", - "buildUrl": "https://ci.example.com/builds/442", - "commit": "f6e5d4c3b2a1", - "extra": { - "branch": "main" - } - } + "reportFormat": "CTRF", + "specVersion": "1.0.0", + "reportId": "9d2c6a10-3f7a-4e22-9a8f-1a2b3c4d5e6f", + "timestamp": "2025-11-24T12:00:00Z", + "generatedBy": "example-ci", + "extra": { + "pipelineStage": "e2e", + "trigger": "pull_request" + }, + "results": { + "tool": { + "name": "example-runner", + "version": "3.5.0", + "extra": { + "plugins": ["retry", "screenshots", "video"] + } + }, + "summary": { + "tests": 3, + "passed": 2, + "failed": 1, + "pending": 0, + "skipped": 0, + "other": 0, + "flaky": 1, + "suites": 2, + "start": 1609459200000, + "stop": 1609459220000, + "duration": 20000 + }, + "tests": [ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "user can login", + "status": "passed", + "duration": 3500, + "start": 1609459200000, + "stop": 1609459203500, + "suite": ["Authentication", "Login"], + "type": "e2e", + "tags": ["smoke", "critical"], + "filePath": "tests/auth/login.test.js", + "browser": "chromium", + "threadId": "worker-1", + "insights": { + "passRate": { + "current": 1.0, + "baseline": 0.95, + "change": 0.05 + }, + "averageTestDuration": { + "current": 3500, + "baseline": 3200, + "change": 300 + }, + "executedInRuns": 15 + } + }, + { + "id": "550e8400-e29b-41d4-a716-446655440001", + "name": "user can checkout", + "status": "passed", + "duration": 5200, + "start": 1609459203500, + "stop": 1609459208700, + "suite": ["Checkout"], + "type": "e2e", + "tags": ["smoke"], + "filePath": "tests/checkout/checkout.test.js", + "browser": "chromium", + "threadId": "worker-2", + "flaky": true, + "retries": 1, + "retryAttempts": [ + { + "attempt": 1, + "status": "failed", + "duration": 5100, + "start": 1609459203500, + "stop": 1609459208600, + "message": "Payment gateway timeout", + "trace": "Error: Payment gateway timeout\n at processPayment (payment.js:23:10)", + "attachments": [ + { + "name": "attempt-1-screenshot.png", + "contentType": "image/png", + "path": "/artifacts/retry-1.png" + } + ] + }, + { + "attempt": 2, + "status": "passed", + "duration": 5200, + "start": 1609459208700, + "stop": 1609459213900 + } + ], + "insights": { + "passRate": { + "current": 1.0, + "baseline": 0.9, + "change": 0.1 + }, + "flakyRate": { + "current": 0.2, + "baseline": 0.3, + "change": -0.1 + }, + "executedInRuns": 15 + } + }, + { + "id": "550e8400-e29b-41d4-a716-446655440002", + "name": "admin dashboard loads", + "status": "failed", + "duration": 8000, + "start": 1609459213900, + "stop": 1609459221900, + "suite": ["Admin", "Dashboard"], + "type": "e2e", + "tags": ["regression"], + "filePath": "tests/admin/dashboard.test.js", + "line": 42, + "browser": "chromium", + "threadId": "worker-1", + "message": "Element not found: .dashboard-metrics", + "trace": "Error: Element not found: .dashboard-metrics\n at waitForElement (test.js:45:10)\n at Object. (dashboard.test.js:42:5)", + "snippet": "const metrics = await page.locator('.dashboard-metrics');\nawait expect(metrics).toBeVisible();", + "screenshot": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", + "stdout": ["Loading admin dashboard", "Checking authentication"], + "stderr": [ + "Warning: Slow network detected", + "Error: Timeout waiting for .dashboard-metrics" + ], + "attachments": [ + { + "name": "failure-screenshot.png", + "contentType": "image/png", + "path": "/artifacts/screenshots/admin-dashboard-fail.png" + }, + { + "name": "trace.zip", + "contentType": "application/zip", + "path": "/artifacts/traces/admin-dashboard.trace.zip" + }, + { + "name": "video.webm", + "contentType": "video/webm", + "path": "/artifacts/videos/admin-dashboard.webm" + } + ], + "parameters": { + "userRole": "admin", + "environment": "staging" + }, + "steps": [ + { + "name": "Navigate to dashboard", + "status": "passed" + }, + { + "name": "Wait for metrics", + "status": "failed" + } + ], + "insights": { + "passRate": { + "current": 0.0, + "baseline": 0.8, + "change": -0.8 + }, + "executedInRuns": 15 + } + } + ], + "environment": { + "reportName": "PR #123 E2E Tests", + "appName": "example-app", + "appVersion": "2.5.0", + "buildId": "build-456", + "buildName": "PR #123", + "buildNumber": 456, + "buildUrl": "https://ci.example.com/builds/456", + "repositoryName": "example-app", + "repositoryUrl": "https://github.com/example/example-app", + "commit": "a1b2c3d4e5f6", + "branchName": "feature/new-dashboard", + "osPlatform": "linux", + "osRelease": "5.10.0", + "osVersion": "Ubuntu 20.04", + "testEnvironment": "staging", + "healthy": false, + "extra": { + "nodeVersion": "18.0.0", + "region": "us-west-2" + } + }, + "extra": { + "shardIndex": 1, + "totalShards": 4 + } + }, + "insights": { + "passRate": { + "current": 0.67, + "baseline": 0.85, + "change": -0.18 + }, + "failRate": { + "current": 0.33, + "baseline": 0.15, + "change": 0.18 + }, + "flakyRate": { + "current": 0.33, + "baseline": 0.2, + "change": 0.13 + }, + "averageRunDuration": { + "current": 20000, + "baseline": 18000, + "change": 2000 + }, + "p95RunDuration": { + "current": 22000, + "baseline": 20000, + "change": 2000 + }, + "averageTestDuration": { + "current": 5567, + "baseline": 5100, + "change": 467 + }, + "runsAnalyzed": 10 + }, + "baseline": { + "reportId": "2f8c9a90-3e1a-4c3d-9b3a-8f0a9c123456", + "timestamp": "2025-11-23T12:00:00Z", + "source": "main", + "buildNumber": 442, + "buildName": "Nightly Build", + "buildUrl": "https://ci.example.com/builds/442", + "commit": "f6e5d4c3b2a1", + "extra": { + "branch": "main" + } + } } diff --git a/examples/minimal.json b/examples/minimal.json index 61b4482..8d59069 100644 --- a/examples/minimal.json +++ b/examples/minimal.json @@ -1,26 +1,26 @@ { - "reportFormat": "CTRF", - "specVersion": "1.0.0", - "results": { - "tool": { - "name": "example-runner" - }, - "summary": { - "tests": 1, - "passed": 1, - "failed": 0, - "pending": 0, - "skipped": 0, - "other": 0, - "start": 1609459200000, - "stop": 1609459201000 - }, - "tests": [ - { - "name": "should pass", - "status": "passed", - "duration": 100 - } - ] - } + "reportFormat": "CTRF", + "specVersion": "1.0.0", + "results": { + "tool": { + "name": "example-runner" + }, + "summary": { + "tests": 1, + "passed": 1, + "failed": 0, + "pending": 0, + "skipped": 0, + "other": 0, + "start": 1609459200000, + "stop": 1609459201000 + }, + "tests": [ + { + "name": "should pass", + "status": "passed", + "duration": 100 + } + ] + } } diff --git a/examples/with-diagnostics.json b/examples/with-diagnostics.json index 20f6946..72e6c93 100644 --- a/examples/with-diagnostics.json +++ b/examples/with-diagnostics.json @@ -1,56 +1,56 @@ { - "reportFormat": "CTRF", - "specVersion": "1.0.0", - "results": { - "tool": { - "name": "example-runner", - "version": "2.0.0" - }, - "summary": { - "tests": 1, - "passed": 0, - "failed": 1, - "pending": 0, - "skipped": 0, - "other": 0, - "start": 1609459200000, - "stop": 1609459205000 - }, - "tests": [ - { - "name": "should render homepage", - "status": "failed", - "duration": 5000, - "message": "Expected element to be visible", - "trace": "Error: Expected element to be visible\n at checkVisibility (test.js:45:10)\n at Object. (test.js:12:5)", - "screenshot": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", - "stdout": [ - "Starting test execution", - "Loading page: https://example.com", - "Waiting for element: .hero-section" - ], - "stderr": [ - "Warning: Network slow", - "Error: Element not found after 5000ms" - ], - "attachments": [ - { - "name": "full-page-screenshot.png", - "contentType": "image/png", - "path": "/artifacts/screenshots/test-1.png" - }, - { - "name": "console-logs.txt", - "contentType": "text/plain", - "path": "/artifacts/logs/console-1.txt" - }, - { - "name": "network-trace.har", - "contentType": "application/json", - "path": "/artifacts/traces/network-1.har" - } - ] - } - ] - } + "reportFormat": "CTRF", + "specVersion": "1.0.0", + "results": { + "tool": { + "name": "example-runner", + "version": "2.0.0" + }, + "summary": { + "tests": 1, + "passed": 0, + "failed": 1, + "pending": 0, + "skipped": 0, + "other": 0, + "start": 1609459200000, + "stop": 1609459205000 + }, + "tests": [ + { + "name": "should render homepage", + "status": "failed", + "duration": 5000, + "message": "Expected element to be visible", + "trace": "Error: Expected element to be visible\n at checkVisibility (test.js:45:10)\n at Object. (test.js:12:5)", + "screenshot": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", + "stdout": [ + "Starting test execution", + "Loading page: https://example.com", + "Waiting for element: .hero-section" + ], + "stderr": [ + "Warning: Network slow", + "Error: Element not found after 5000ms" + ], + "attachments": [ + { + "name": "full-page-screenshot.png", + "contentType": "image/png", + "path": "/artifacts/screenshots/test-1.png" + }, + { + "name": "console-logs.txt", + "contentType": "text/plain", + "path": "/artifacts/logs/console-1.txt" + }, + { + "name": "network-trace.har", + "contentType": "application/json", + "path": "/artifacts/traces/network-1.har" + } + ] + } + ] + } } diff --git a/examples/with-insights.json b/examples/with-insights.json index 690c7ae..3e612d3 100644 --- a/examples/with-insights.json +++ b/examples/with-insights.json @@ -1,78 +1,78 @@ { - "reportFormat": "CTRF", - "specVersion": "1.0.0", - "reportId": "7c4e1c20-7c89-4f30-9b52-1f6f9d6b8f21", - "results": { - "tool": { - "name": "example-runner", - "version": "3.0.0" - }, - "summary": { - "tests": 2, - "passed": 2, - "failed": 0, - "pending": 0, - "skipped": 0, - "other": 0, - "start": 1609459200000, - "stop": 1609459205000 - }, - "tests": [ - { - "name": "login test", - "status": "passed", - "duration": 2500, - "insights": { - "passRate": { - "current": 1.0, - "baseline": 0.95, - "change": 0.05 - }, - "averageTestDuration": { - "current": 2500, - "baseline": 2200, - "change": 300 - }, - "executedInRuns": 10 - } - }, - { - "name": "checkout test", - "status": "passed", - "duration": 3200, - "insights": { - "passRate": { - "current": 1.0, - "baseline": 1.0, - "change": 0 - }, - "averageTestDuration": { - "current": 3200, - "baseline": 3100, - "change": 100 - }, - "executedInRuns": 10 - } - } - ] - }, - "insights": { - "passRate": { - "current": 1.0, - "baseline": 0.97, - "change": 0.03 - }, - "averageRunDuration": { - "current": 5700, - "baseline": 5300, - "change": 400 - }, - "runsAnalyzed": 10 - }, - "baseline": { - "reportId": "2f8c9a90-3e1a-4c3d-9b3a-8f0a9c123456", - "timestamp": "2025-11-23T12:00:00Z", - "source": "main", - "buildNumber": 142 - } + "reportFormat": "CTRF", + "specVersion": "1.0.0", + "reportId": "7c4e1c20-7c89-4f30-9b52-1f6f9d6b8f21", + "results": { + "tool": { + "name": "example-runner", + "version": "3.0.0" + }, + "summary": { + "tests": 2, + "passed": 2, + "failed": 0, + "pending": 0, + "skipped": 0, + "other": 0, + "start": 1609459200000, + "stop": 1609459205000 + }, + "tests": [ + { + "name": "login test", + "status": "passed", + "duration": 2500, + "insights": { + "passRate": { + "current": 1.0, + "baseline": 0.95, + "change": 0.05 + }, + "averageTestDuration": { + "current": 2500, + "baseline": 2200, + "change": 300 + }, + "executedInRuns": 10 + } + }, + { + "name": "checkout test", + "status": "passed", + "duration": 3200, + "insights": { + "passRate": { + "current": 1.0, + "baseline": 1.0, + "change": 0 + }, + "averageTestDuration": { + "current": 3200, + "baseline": 3100, + "change": 100 + }, + "executedInRuns": 10 + } + } + ] + }, + "insights": { + "passRate": { + "current": 1.0, + "baseline": 0.97, + "change": 0.03 + }, + "averageRunDuration": { + "current": 5700, + "baseline": 5300, + "change": 400 + }, + "runsAnalyzed": 10 + }, + "baseline": { + "reportId": "2f8c9a90-3e1a-4c3d-9b3a-8f0a9c123456", + "timestamp": "2025-11-23T12:00:00Z", + "source": "main", + "buildNumber": 142 + } } diff --git a/examples/with-retries.json b/examples/with-retries.json index b237970..a90ef55 100644 --- a/examples/with-retries.json +++ b/examples/with-retries.json @@ -1,54 +1,54 @@ { - "reportFormat": "CTRF", - "specVersion": "1.0.0", - "results": { - "tool": { - "name": "example-runner", - "version": "1.0.0" - }, - "summary": { - "tests": 2, - "passed": 2, - "failed": 0, - "pending": 0, - "skipped": 0, - "other": 0, - "flaky": 1, - "start": 1609459200000, - "stop": 1609459210000 - }, - "tests": [ - { - "name": "stable test", - "status": "passed", - "duration": 150 - }, - { - "name": "flaky test", - "status": "passed", - "duration": 300, - "flaky": true, - "retries": 2, - "retryAttempts": [ - { - "attempt": 1, - "status": "failed", - "duration": 120, - "message": "Connection timeout" - }, - { - "attempt": 2, - "status": "failed", - "duration": 130, - "message": "Connection timeout" - }, - { - "attempt": 3, - "status": "passed", - "duration": 140 - } - ] - } - ] - } + "reportFormat": "CTRF", + "specVersion": "1.0.0", + "results": { + "tool": { + "name": "example-runner", + "version": "1.0.0" + }, + "summary": { + "tests": 2, + "passed": 2, + "failed": 0, + "pending": 0, + "skipped": 0, + "other": 0, + "flaky": 1, + "start": 1609459200000, + "stop": 1609459210000 + }, + "tests": [ + { + "name": "stable test", + "status": "passed", + "duration": 150 + }, + { + "name": "flaky test", + "status": "passed", + "duration": 300, + "flaky": true, + "retries": 2, + "retryAttempts": [ + { + "attempt": 1, + "status": "failed", + "duration": 120, + "message": "Connection timeout" + }, + { + "attempt": 2, + "status": "failed", + "duration": 130, + "message": "Connection timeout" + }, + { + "attempt": 3, + "status": "passed", + "duration": 140 + } + ] + } + ] + } } diff --git a/package-lock.json b/package-lock.json index 2c94273..511caff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3160 +1,2069 @@ { - "name": "ctrf-cli", - "version": "0.0.5", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "ctrf-cli", - "version": "0.0.5", - "license": "MIT", - "dependencies": { - "ctrf": "^0.2.0", - "glob": "^13.0.1", - "typescript": "^5.4.5", - "yargs": "^17.7.2" - }, - "bin": { - "ctrf": "dist/cli.js", - "ctrf-cli": "dist/cli.js" - }, - "devDependencies": { - "@d2t/vitest-ctrf-json-reporter": "^1.2.0", - "@eslint/js": "^10.0.1", - "@types/node": "^25.0.10", - "@types/yargs": "^17.0.32", - "@vitest/coverage-v8": "^4.0.18", - "eslint": "^10.0.2", - "prettier": "^3.5.3", - "typescript": "^5.4.5", - "typescript-eslint": "^8.57.0", - "vitest": "^4.0.18" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@d2t/vitest-ctrf-json-reporter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@d2t/vitest-ctrf-json-reporter/-/vitest-ctrf-json-reporter-1.3.0.tgz", - "integrity": "sha512-3Xgkay0Hubq1hA67IW2vM3YhX4TQgjOFW2TydB0ytAL97zOZI9xr/9vqCjo31bK1qUDNEdlKYLYHd8KR9YSCCw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.23.5", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", - "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.5", - "debug": "^4.3.1", - "minimatch": "^10.2.4" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", - "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/@eslint/object-schema": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", - "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", - "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1", - "levn": "^0.4.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.9.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", - "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", - "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/type-utils": "8.58.0", - "@typescript-eslint/utils": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.58.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", - "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", - "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.0", - "@typescript-eslint/types": "^8.58.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", - "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", - "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", - "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/utils": "8.58.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", - "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", - "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.58.0", - "@typescript-eslint/tsconfig-utils": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", - "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", - "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", - "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.8", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.1.8", - "vitest": "4.1.8" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", - "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", - "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.8", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", - "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", - "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.8", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", - "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.8", - "@vitest/utils": "4.1.8", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", - "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", - "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.8", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/ast-v8-to-istanbul": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", - "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/ctrf": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/ctrf/-/ctrf-0.2.0.tgz", - "integrity": "sha512-3tm25Qj6U4Ih10p+rXbobGmQ8udNVGfNnuQ9/L648t5641LkuHcDU6ZTnwClJujhd14xvWc6LPWc57aiYau0tA==", - "license": "MIT", - "dependencies": { - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "glob": "^13.0.0", - "yargs": "^18.0.0" - }, - "bin": { - "ctrf": "dist/cli/cli.js" - }, - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/ctrf/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ctrf/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ctrf/node_modules/cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "license": "ISC", - "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/ctrf/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "node_modules/ctrf/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ctrf/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/ctrf/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/ctrf/node_modules/yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", - "license": "MIT", - "dependencies": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/ctrf/node_modules/yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "license": "ISC", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz", - "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", - "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.2", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", - "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.133.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", - "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.58.0", - "@typescript-eslint/parser": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/utils": "8.58.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "dev": true, - "license": "MIT" - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", - "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.8", - "@vitest/mocker": "4.1.8", - "@vitest/pretty-format": "4.1.8", - "@vitest/runner": "4.1.8", - "@vitest/snapshot": "4.1.8", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.8", - "@vitest/browser-preview": "4.1.8", - "@vitest/browser-webdriverio": "4.1.8", - "@vitest/coverage-istanbul": "4.1.8", - "@vitest/coverage-v8": "4.1.8", - "@vitest/ui": "4.1.8", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } + "name": "ctrf-cli", + "version": "0.0.5", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ctrf-cli", + "version": "0.0.5", + "license": "MIT", + "dependencies": { + "ctrf": "0.2.1", + "glob": "13.0.6", + "yargs": "18.0.0" + }, + "bin": { + "ctrf": "dist/cli.js", + "ctrf-cli": "dist/cli.js" + }, + "devDependencies": { + "@biomejs/biome": "2.4.16", + "@d2t/vitest-ctrf-json-reporter": "1.3.0", + "@types/node": "25.9.2", + "@types/yargs": "17.0.35", + "@vitest/coverage-v8": "4.1.8", + "typescript": "6.0.3", + "vitest": "4.1.8" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@biomejs/biome": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.16.tgz", + "integrity": "sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.4.16", + "@biomejs/cli-darwin-x64": "2.4.16", + "@biomejs/cli-linux-arm64": "2.4.16", + "@biomejs/cli-linux-arm64-musl": "2.4.16", + "@biomejs/cli-linux-x64": "2.4.16", + "@biomejs/cli-linux-x64-musl": "2.4.16", + "@biomejs/cli-win32-arm64": "2.4.16", + "@biomejs/cli-win32-x64": "2.4.16" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.16.tgz", + "integrity": "sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.16.tgz", + "integrity": "sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.16.tgz", + "integrity": "sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.16.tgz", + "integrity": "sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.16.tgz", + "integrity": "sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.16.tgz", + "integrity": "sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.16.tgz", + "integrity": "sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.16.tgz", + "integrity": "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@d2t/vitest-ctrf-json-reporter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@d2t/vitest-ctrf-json-reporter/-/vitest-ctrf-json-reporter-1.3.0.tgz", + "integrity": "sha512-3Xgkay0Hubq1hA67IW2vM3YhX4TQgjOFW2TydB0ytAL97zOZI9xr/9vqCjo31bK1qUDNEdlKYLYHd8KR9YSCCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", + "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", + "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.8", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.8", + "vitest": "4.1.8" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", + "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", + "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", + "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", + "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", + "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", + "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", + "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ctrf": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ctrf/-/ctrf-0.2.1.tgz", + "integrity": "sha512-iUo/eHcM5yG8aBS3Miqce9NNiZCtmVZxPpgmZEJIZ96bubwj7IpZx3IqsDqCH2FZjR71EH2NLtbBhtfzDjpaUg==", + "license": "MIT", + "dependencies": { + "ajv": "8.20.0", + "ajv-formats": "3.0.1", + "glob": "13.0.6", + "yargs": "18.0.0" + }, + "bin": { + "ctrf": "dist/cli/cli.js" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", + "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + } + } } diff --git a/package.json b/package.json index dd1e235..1bbdcd1 100644 --- a/package.json +++ b/package.json @@ -1,70 +1,70 @@ { - "type": "module", - "name": "ctrf-cli", - "version": "0.0.5", - "description": "Various CTRF utilities available from the command line", - "main": "dist/index.js", - "bin": { - "ctrf": "dist/cli.js", - "ctrf-cli": "dist/cli.js" - }, - "scripts": { - "build": "tsc", - "test": "vitest run", - "test:coverage": "vitest run --coverage", - "lint": "eslint . --ext .ts --fix", - "lint:check": "eslint . --ext .ts", - "format": "prettier --write .", - "format:check": "prettier --check .", - "all": "npm run lint && npm run format:check && npm run test:coverage && npm run build" - }, - "files": [ - "dist/", - "README.md" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/ctrf-io/ctrf-cli.git" - }, - "publishConfig": { - "access": "public", - "provenance": true - }, - "homepage": "https://ctrf.io", - "author": "Matthew Poulton-White", - "license": "MIT", - "security": { - "email": "security@ctrf.io", - "url": "https://github.com/ctrf-io/security/blob/main/SECURITY.md" - }, - "keywords": [ - "test", - "testing", - "ctrf", - "reporter", - "report", - "json", - "standard", - "cli", - "command-line", - "test-results" - ], - "dependencies": { - "ctrf": "^0.2.0", - "glob": "^13.0.1", - "typescript": "^5.4.5", - "yargs": "^17.7.2" - }, - "devDependencies": { - "@d2t/vitest-ctrf-json-reporter": "^1.2.0", - "@eslint/js": "^10.0.1", - "@types/node": "^25.0.10", - "@types/yargs": "^17.0.32", - "@vitest/coverage-v8": "^4.0.18", - "eslint": "^10.0.2", - "prettier": "^3.5.3", - "typescript": "^5.4.5", - "typescript-eslint": "^8.57.0", - "vitest": "^4.0.18" - } + "type": "module", + "name": "ctrf-cli", + "version": "0.0.5", + "description": "Various CTRF utilities available from the command line", + "main": "dist/index.js", + "bin": { + "ctrf": "dist/cli.js", + "ctrf-cli": "dist/cli.js" + }, + "scripts": { + "build": "tsc", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "lint": "biome lint --write . --unsafe", + "lint:check": "biome lint .", + "format": "biome format --write .", + "format:check": "biome format .", + "check": "biome check .", + "all": "npm run lint && npm run format:check && npm run test:coverage && npm run build" + }, + "files": [ + "dist/", + "README.md" + ], + "engines": { + "node": ">=20.19.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ctrf-io/ctrf-cli.git" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "homepage": "https://ctrf.io", + "author": "Matthew Poulton-White", + "license": "MIT", + "security": { + "email": "security@ctrf.io", + "url": "https://github.com/ctrf-io/security/blob/main/SECURITY.md" + }, + "keywords": [ + "test", + "testing", + "ctrf", + "reporter", + "report", + "json", + "standard", + "cli", + "command-line", + "test-results" + ], + "dependencies": { + "ctrf": "0.2.1", + "glob": "13.0.6", + "yargs": "18.0.0" + }, + "devDependencies": { + "@biomejs/biome": "2.4.16", + "@d2t/vitest-ctrf-json-reporter": "1.3.0", + "@types/node": "25.9.2", + "@types/yargs": "17.0.35", + "@vitest/coverage-v8": "4.1.8", + "typescript": "6.0.3", + "vitest": "4.1.8" + } } diff --git a/src/add-insights.test.ts b/src/add-insights.test.ts index 8b8464c..62d800f 100644 --- a/src/add-insights.test.ts +++ b/src/add-insights.test.ts @@ -1,233 +1,233 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import fs from 'fs' -import path from 'path' -import os from 'os' -import { ReportBuilder, TestBuilder, addInsights, stringify, parse } from 'ctrf' -import { addInsightsCommand } from './add-insights.js' - -describe('addInsightsCommand', () => { - let tmpDir: string - let currentReportPath: string - let historicalReportsDir: string - let exitSpy: ReturnType - let consoleLogSpy: ReturnType - let consoleErrorSpy: ReturnType - let consoleWarnSpy: ReturnType - - const createReport = ( - index: number, - passedCount: number, - failedCount: number - ) => { - const builder = new ReportBuilder().tool({ name: 'test-tool' }) - - for (let i = 0; i < passedCount; i++) { - builder.addTest( - new TestBuilder() - .name(`test ${i + 1}`) - .status('passed') - .duration(100 + i * 10) - .build() - ) - } - - for (let i = 0; i < failedCount; i++) { - builder.addTest( - new TestBuilder() - .name(`test ${passedCount + i + 1}`) - .status('failed') - .duration(200 + i * 10) - .build() - ) - } - - return builder.build() - } - - beforeEach(() => { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ctrf-insights-test-')) - historicalReportsDir = path.join(tmpDir, 'historical') - fs.mkdirSync(historicalReportsDir, { recursive: true }) - - fs.writeFileSync( - path.join(historicalReportsDir, 'report1.json'), - JSON.stringify(createReport(1, 8, 2), null, 2) - ) - fs.writeFileSync( - path.join(historicalReportsDir, 'report2.json'), - JSON.stringify(createReport(2, 7, 3), null, 2) - ) - - currentReportPath = path.join(tmpDir, 'current-report.json') - fs.writeFileSync( - currentReportPath, - JSON.stringify(createReport(3, 9, 1), null, 2) - ) - - exitSpy = vi - .spyOn(process, 'exit') - .mockImplementation((() => undefined as never) as any) as any - consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) - consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) - consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) - }) - - afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }) - exitSpy.mockRestore() - consoleLogSpy.mockRestore() - consoleErrorSpy.mockRestore() - consoleWarnSpy.mockRestore() - }) - - describe('insights generation', () => { - it('should analyze historical reports and add insights to current report', async () => { - await addInsightsCommand(currentReportPath, historicalReportsDir) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] as string - const result = JSON.parse(output) - - expect(result.reportFormat).toBe('CTRF') - expect(result.results).toBeDefined() - }) - - it('should produce valid CTRF output', async () => { - await addInsightsCommand(currentReportPath, historicalReportsDir) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.reportFormat).toBe('CTRF') - expect(result.specVersion).toBeDefined() - expect(result.results).toBeDefined() - expect(result.results.tool).toBeDefined() - expect(result.results.summary).toBeDefined() - expect(result.results.tests).toBeDefined() - }) - }) - - describe('output option', () => { - it('should write to file when --output is specified', async () => { - const outputPath = path.join(tmpDir, 'with-insights.json') - - await addInsightsCommand(currentReportPath, historicalReportsDir, { - output: outputPath, - }) - expect(exitSpy).toHaveBeenCalledWith(0) - - expect(fs.existsSync(outputPath)).toBe(true) - - const savedContent = JSON.parse(fs.readFileSync(outputPath, 'utf-8')) - expect(savedContent.reportFormat).toBe('CTRF') - - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('Analyzed') - ) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('Saved to') - ) - }) - - it('should print to stdout when no --output specified', async () => { - await addInsightsCommand(currentReportPath, historicalReportsDir) - expect(exitSpy).toHaveBeenCalledWith(0) - - expect(consoleLogSpy).toHaveBeenCalled() - const output = consoleLogSpy.mock.calls[0][0] - expect(() => JSON.parse(output as string)).not.toThrow() - }) - }) - - describe('error handling', () => { - it('should exit with code 3 for current report not found', async () => { - const nonExistentReportPath = path.join(tmpDir, 'nonexistent.json') - await addInsightsCommand(nonExistentReportPath, historicalReportsDir) - expect(exitSpy).toHaveBeenCalledWith(3) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('Report file not found') - ) - }) - - it('should exit with code 3 for historical directory not found', async () => { - const nonExistentDir = path.join(tmpDir, 'nonexistent') - await addInsightsCommand(currentReportPath, nonExistentDir) - expect(exitSpy).toHaveBeenCalledWith(3) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('Directory not found') - ) - }) - - it('should warn but succeed when no valid historical reports found', async () => { - const emptyDir = path.join(tmpDir, 'empty') - fs.mkdirSync(emptyDir, { recursive: true }) - - await addInsightsCommand(currentReportPath, emptyDir) - expect(exitSpy).toHaveBeenCalledWith(0) - expect(consoleWarnSpy).toHaveBeenCalledWith( - expect.stringContaining('No valid CTRF historical reports found') - ) - }) - - it('should skip non-CTRF files with warning', async () => { - fs.writeFileSync( - path.join(historicalReportsDir, 'not-ctrf.json'), - JSON.stringify({ foo: 'bar' }) - ) - - await addInsightsCommand(currentReportPath, historicalReportsDir) - expect(exitSpy).toHaveBeenCalledWith(0) - - expect(consoleLogSpy).toHaveBeenCalled() - }) - - it('should skip invalid JSON files', async () => { - fs.writeFileSync( - path.join(historicalReportsDir, 'invalid.json'), - 'not valid json' - ) - - await addInsightsCommand(currentReportPath, historicalReportsDir) - expect(exitSpy).toHaveBeenCalledWith(0) - - expect(consoleLogSpy).toHaveBeenCalled() - }) - }) - - describe('single historical report', () => { - it('should work with a single historical report', async () => { - await addInsightsCommand(currentReportPath, historicalReportsDir) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.reportFormat).toBe('CTRF') - }) - }) - - describe('current report in historical directory', () => { - it('should exclude current report if it has the same absolute path', async () => { - const parentDir = tmpDir - const reportsDirAsParent = path.join(parentDir, 'reports') - fs.mkdirSync(reportsDirAsParent, { recursive: true }) - - const reportInParent = path.join(parentDir, 'report.json') - fs.writeFileSync( - reportInParent, - JSON.stringify(createReport(1, 8, 2), null, 2) - ) - - fs.writeFileSync( - path.join(reportsDirAsParent, 'historical.json'), - JSON.stringify(createReport(2, 7, 3), null, 2) - ) - - await addInsightsCommand(reportInParent, reportsDirAsParent) - expect(exitSpy).toHaveBeenCalledWith(0) - - expect(consoleLogSpy).toHaveBeenCalled() - }) - }) -}) +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { ReportBuilder, TestBuilder } from "ctrf"; +import { addInsightsCommand } from "./add-insights.js"; + +describe("addInsightsCommand", () => { + let tmpDir: string; + let currentReportPath: string; + let historicalReportsDir: string; + let exitSpy: ReturnType; + let consoleLogSpy: ReturnType; + let consoleErrorSpy: ReturnType; + let consoleWarnSpy: ReturnType; + + const createReport = ( + _index: number, + passedCount: number, + failedCount: number, + ) => { + const builder = new ReportBuilder().tool({ name: "test-tool" }); + + for (let i = 0; i < passedCount; i++) { + builder.addTest( + new TestBuilder() + .name(`test ${i + 1}`) + .status("passed") + .duration(100 + i * 10) + .build(), + ); + } + + for (let i = 0; i < failedCount; i++) { + builder.addTest( + new TestBuilder() + .name(`test ${passedCount + i + 1}`) + .status("failed") + .duration(200 + i * 10) + .build(), + ); + } + + return builder.build(); + }; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ctrf-insights-test-")); + historicalReportsDir = path.join(tmpDir, "historical"); + fs.mkdirSync(historicalReportsDir, { recursive: true }); + + fs.writeFileSync( + path.join(historicalReportsDir, "report1.json"), + JSON.stringify(createReport(1, 8, 2), null, 2), + ); + fs.writeFileSync( + path.join(historicalReportsDir, "report2.json"), + JSON.stringify(createReport(2, 7, 3), null, 2), + ); + + currentReportPath = path.join(tmpDir, "current-report.json"); + fs.writeFileSync( + currentReportPath, + JSON.stringify(createReport(3, 9, 1), null, 2), + ); + + exitSpy = vi + .spyOn(process, "exit") + .mockImplementation((() => undefined as never) as any) as any; + consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + exitSpy.mockRestore(); + consoleLogSpy.mockRestore(); + consoleErrorSpy.mockRestore(); + consoleWarnSpy.mockRestore(); + }); + + describe("insights generation", () => { + it("should analyze historical reports and add insights to current report", async () => { + await addInsightsCommand(currentReportPath, historicalReportsDir); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0] as string; + const result = JSON.parse(output); + + expect(result.reportFormat).toBe("CTRF"); + expect(result.results).toBeDefined(); + }); + + it("should produce valid CTRF output", async () => { + await addInsightsCommand(currentReportPath, historicalReportsDir); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.reportFormat).toBe("CTRF"); + expect(result.specVersion).toBeDefined(); + expect(result.results).toBeDefined(); + expect(result.results.tool).toBeDefined(); + expect(result.results.summary).toBeDefined(); + expect(result.results.tests).toBeDefined(); + }); + }); + + describe("output option", () => { + it("should write to file when --output is specified", async () => { + const outputPath = path.join(tmpDir, "with-insights.json"); + + await addInsightsCommand(currentReportPath, historicalReportsDir, { + output: outputPath, + }); + expect(exitSpy).toHaveBeenCalledWith(0); + + expect(fs.existsSync(outputPath)).toBe(true); + + const savedContent = JSON.parse(fs.readFileSync(outputPath, "utf-8")); + expect(savedContent.reportFormat).toBe("CTRF"); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Analyzed"), + ); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Saved to"), + ); + }); + + it("should print to stdout when no --output specified", async () => { + await addInsightsCommand(currentReportPath, historicalReportsDir); + expect(exitSpy).toHaveBeenCalledWith(0); + + expect(consoleLogSpy).toHaveBeenCalled(); + const output = consoleLogSpy.mock.calls[0][0]; + expect(() => JSON.parse(output as string)).not.toThrow(); + }); + }); + + describe("error handling", () => { + it("should exit with code 3 for current report not found", async () => { + const nonExistentReportPath = path.join(tmpDir, "nonexistent.json"); + await addInsightsCommand(nonExistentReportPath, historicalReportsDir); + expect(exitSpy).toHaveBeenCalledWith(3); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Report file not found"), + ); + }); + + it("should exit with code 3 for historical directory not found", async () => { + const nonExistentDir = path.join(tmpDir, "nonexistent"); + await addInsightsCommand(currentReportPath, nonExistentDir); + expect(exitSpy).toHaveBeenCalledWith(3); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Directory not found"), + ); + }); + + it("should warn but succeed when no valid historical reports found", async () => { + const emptyDir = path.join(tmpDir, "empty"); + fs.mkdirSync(emptyDir, { recursive: true }); + + await addInsightsCommand(currentReportPath, emptyDir); + expect(exitSpy).toHaveBeenCalledWith(0); + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining("No valid CTRF historical reports found"), + ); + }); + + it("should skip non-CTRF files with warning", async () => { + fs.writeFileSync( + path.join(historicalReportsDir, "not-ctrf.json"), + JSON.stringify({ foo: "bar" }), + ); + + await addInsightsCommand(currentReportPath, historicalReportsDir); + expect(exitSpy).toHaveBeenCalledWith(0); + + expect(consoleLogSpy).toHaveBeenCalled(); + }); + + it("should skip invalid JSON files", async () => { + fs.writeFileSync( + path.join(historicalReportsDir, "invalid.json"), + "not valid json", + ); + + await addInsightsCommand(currentReportPath, historicalReportsDir); + expect(exitSpy).toHaveBeenCalledWith(0); + + expect(consoleLogSpy).toHaveBeenCalled(); + }); + }); + + describe("single historical report", () => { + it("should work with a single historical report", async () => { + await addInsightsCommand(currentReportPath, historicalReportsDir); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.reportFormat).toBe("CTRF"); + }); + }); + + describe("current report in historical directory", () => { + it("should exclude current report if it has the same absolute path", async () => { + const parentDir = tmpDir; + const reportsDirAsParent = path.join(parentDir, "reports"); + fs.mkdirSync(reportsDirAsParent, { recursive: true }); + + const reportInParent = path.join(parentDir, "report.json"); + fs.writeFileSync( + reportInParent, + JSON.stringify(createReport(1, 8, 2), null, 2), + ); + + fs.writeFileSync( + path.join(reportsDirAsParent, "historical.json"), + JSON.stringify(createReport(2, 7, 3), null, 2), + ); + + await addInsightsCommand(reportInParent, reportsDirAsParent); + expect(exitSpy).toHaveBeenCalledWith(0); + + expect(consoleLogSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/add-insights.ts b/src/add-insights.ts index 63f99c3..865b636 100644 --- a/src/add-insights.ts +++ b/src/add-insights.ts @@ -1,133 +1,129 @@ -import fs from 'fs' -import path from 'path' -import { parse, addInsights, stringify, CTRFReport } from 'ctrf' +import fs from "node:fs"; +import path from "node:path"; +import { parse, addInsights, stringify, type CTRFReport } from "ctrf"; -const EXIT_SUCCESS = 0 -const EXIT_GENERAL_ERROR = 1 -const EXIT_FILE_NOT_FOUND = 3 +const EXIT_SUCCESS = 0; +const EXIT_GENERAL_ERROR = 1; +const EXIT_FILE_NOT_FOUND = 3; export interface AddInsightsOptions { - output?: string + output?: string; } export async function addInsightsCommand( - currentReportPath: string, - historicalReportsDirectory: string, - options: AddInsightsOptions = {} + currentReportPath: string, + historicalReportsDirectory: string, + options: AddInsightsOptions = {}, ): Promise { - try { - const resolvedCurrentPath = path.resolve(currentReportPath) - const resolvedHistoricalDir = path.resolve(historicalReportsDirectory) - - if (!fs.existsSync(resolvedCurrentPath)) { - console.error(`Error: Report file not found: ${resolvedCurrentPath}`) - process.exit(EXIT_FILE_NOT_FOUND) - } - - if (!fs.statSync(resolvedCurrentPath).isFile()) { - console.error(`Error: Path is not a file: ${resolvedCurrentPath}`) - process.exit(EXIT_GENERAL_ERROR) - } - - if (!fs.existsSync(resolvedHistoricalDir)) { - console.error(`Error: Directory not found: ${resolvedHistoricalDir}`) - process.exit(EXIT_FILE_NOT_FOUND) - } - - if (!fs.statSync(resolvedHistoricalDir).isDirectory()) { - console.error(`Error: Path is not a directory: ${resolvedHistoricalDir}`) - process.exit(EXIT_GENERAL_ERROR) - } - - let currentReport: CTRFReport - try { - const currentContent = fs.readFileSync(resolvedCurrentPath, 'utf-8') - currentReport = parse(currentContent) - - if ( - !currentReport || - !currentReport.results || - !currentReport.results.tests - ) { - console.error(`Error: Invalid CTRF report: ${resolvedCurrentPath}`) - process.exit(EXIT_GENERAL_ERROR) - } - } catch { - console.error( - `Error: Failed to parse current report: ${resolvedCurrentPath}` - ) - process.exit(EXIT_GENERAL_ERROR) - } - - const files = fs.readdirSync(resolvedHistoricalDir) - const historicalReports: CTRFReport[] = [] - let totalHistoricalTests = 0 - - for (const file of files) { - if (path.extname(file) !== '.json') { - continue - } - - const filePath = path.join(resolvedHistoricalDir, file) - const resolvedFilePath = path.resolve(filePath) - - if ( - resolvedFilePath.toLowerCase() === resolvedCurrentPath.toLowerCase() - ) { - console.warn( - 'Note: Current report found in historical reports directory and excluded from analysis' - ) - continue - } - - try { - const fileContent = fs.readFileSync(filePath, 'utf-8') - const report = parse(fileContent) - - if (report && report.results && report.results.tests) { - historicalReports.push(report) - totalHistoricalTests += report.results.tests.length - } else { - console.warn(`Skipping non-CTRF file: ${file}`) - } - } catch { - console.warn(`Skipping invalid file: ${file}`) - } - } - - if (historicalReports.length === 0) { - console.warn( - 'No valid CTRF historical reports found in the specified directory.' - ) - } - - const reportWithInsights = addInsights(currentReport, historicalReports) - - if (options.output) { - const outputPath = path.resolve(options.output) - const outputDir = path.dirname(outputPath) - - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }) - } - - fs.writeFileSync(outputPath, stringify(reportWithInsights), 'utf-8') - - console.error( - `✓ Analyzed current report with ${historicalReports.length} historical report(s) (${totalHistoricalTests} total tests)` - ) - console.error( - `✓ Added insights including trends, patterns, and behavioral analysis` - ) - console.error(`✓ Saved to ${options.output}`) - process.exit(EXIT_SUCCESS) - } else { - const output = stringify(reportWithInsights) - console.log(output) - process.exit(EXIT_SUCCESS) - } - } catch (error) { - console.error(`Error: ${(error as Error).message}`) - process.exit(EXIT_GENERAL_ERROR) - } + try { + const resolvedCurrentPath = path.resolve(currentReportPath); + const resolvedHistoricalDir = path.resolve(historicalReportsDirectory); + + if (!fs.existsSync(resolvedCurrentPath)) { + console.error(`Error: Report file not found: ${resolvedCurrentPath}`); + process.exit(EXIT_FILE_NOT_FOUND); + } + + if (!fs.statSync(resolvedCurrentPath).isFile()) { + console.error(`Error: Path is not a file: ${resolvedCurrentPath}`); + process.exit(EXIT_GENERAL_ERROR); + } + + if (!fs.existsSync(resolvedHistoricalDir)) { + console.error(`Error: Directory not found: ${resolvedHistoricalDir}`); + process.exit(EXIT_FILE_NOT_FOUND); + } + + if (!fs.statSync(resolvedHistoricalDir).isDirectory()) { + console.error(`Error: Path is not a directory: ${resolvedHistoricalDir}`); + process.exit(EXIT_GENERAL_ERROR); + } + + let currentReport: CTRFReport; + try { + const currentContent = fs.readFileSync(resolvedCurrentPath, "utf-8"); + currentReport = parse(currentContent); + + if (!currentReport?.results?.tests) { + console.error(`Error: Invalid CTRF report: ${resolvedCurrentPath}`); + process.exit(EXIT_GENERAL_ERROR); + } + } catch { + console.error( + `Error: Failed to parse current report: ${resolvedCurrentPath}`, + ); + process.exit(EXIT_GENERAL_ERROR); + } + + const files = fs.readdirSync(resolvedHistoricalDir); + const historicalReports: CTRFReport[] = []; + let totalHistoricalTests = 0; + + for (const file of files) { + if (path.extname(file) !== ".json") { + continue; + } + + const filePath = path.join(resolvedHistoricalDir, file); + const resolvedFilePath = path.resolve(filePath); + + if ( + resolvedFilePath.toLowerCase() === resolvedCurrentPath.toLowerCase() + ) { + console.warn( + "Note: Current report found in historical reports directory and excluded from analysis", + ); + continue; + } + + try { + const fileContent = fs.readFileSync(filePath, "utf-8"); + const report = parse(fileContent); + + if (report?.results?.tests) { + historicalReports.push(report); + totalHistoricalTests += report.results.tests.length; + } else { + console.warn(`Skipping non-CTRF file: ${file}`); + } + } catch { + console.warn(`Skipping invalid file: ${file}`); + } + } + + if (historicalReports.length === 0) { + console.warn( + "No valid CTRF historical reports found in the specified directory.", + ); + } + + const reportWithInsights = addInsights(currentReport, historicalReports); + + if (options.output) { + const outputPath = path.resolve(options.output); + const outputDir = path.dirname(outputPath); + + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + fs.writeFileSync(outputPath, stringify(reportWithInsights), "utf-8"); + + console.error( + `✓ Analyzed current report with ${historicalReports.length} historical report(s) (${totalHistoricalTests} total tests)`, + ); + console.error( + `✓ Added insights including trends, patterns, and behavioral analysis`, + ); + console.error(`✓ Saved to ${options.output}`); + process.exit(EXIT_SUCCESS); + } else { + const output = stringify(reportWithInsights); + console.log(output); + process.exit(EXIT_SUCCESS); + } + } catch (error) { + console.error(`Error: ${(error as Error).message}`); + process.exit(EXIT_GENERAL_ERROR); + } } diff --git a/src/cli.ts b/src/cli.ts index ef4ae94..fe236b0 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,240 +1,240 @@ #!/usr/bin/env node -import yargs from 'yargs/yargs' -import { hideBin } from 'yargs/helpers' -import { mergeReports } from './merge.js' -import { identifyFlakyTests } from './flaky.js' -import { validateReport } from './validate.js' -import { filterReport } from './filter.js' -import { generateTestIds } from './generate-test-ids.js' -import { generateReportIdCommand } from './generate-report-id.js' -import { addInsightsCommand } from './add-insights.js' +import yargs from "yargs/yargs"; +import { hideBin } from "yargs/helpers"; +import { mergeReports } from "./merge.js"; +import { identifyFlakyTests } from "./flaky.js"; +import { validateReport } from "./validate.js"; +import { filterReport } from "./filter.js"; +import { generateTestIds } from "./generate-test-ids.js"; +import { generateReportIdCommand } from "./generate-report-id.js"; +import { addInsightsCommand } from "./add-insights.js"; -const argv = yargs(hideBin(process.argv)) - .command( - 'merge ', - 'Merge CTRF reports into a single report', - yargs => { - return yargs - .positional('directory', { - describe: 'Directory of the CTRF reports', - type: 'string', - demandOption: true, - }) - .option('output', { - alias: 'o', - describe: 'Output file path (default: ctrf-report.json)', - type: 'string', - default: 'ctrf-report.json', - }) - .option('output-dir', { - alias: 'd', - describe: 'Output directory for merged report', - type: 'string', - hidden: true, - deprecated: 'Use --output with a path instead', - }) - .option('keep-reports', { - alias: 'k', - describe: 'Keep existing reports after merging', - type: 'boolean', - default: false, - }) - }, - async argv => { - await mergeReports( - argv.directory as string, - argv.output as string, - argv['keep-reports'] as boolean, - argv['output-dir'] as string - ) - } - ) - .command( - 'flaky ', - 'Identify flaky tests from a CTRF report file', - yargs => { - return yargs.positional('file', { - describe: 'CTRF report file', - type: 'string', - demandOption: true, - }) - }, - async argv => { - await identifyFlakyTests(argv.file as string) - } - ) - .command( - 'validate ', - 'Validate a CTRF report against the JSON schema', - yargs => { - return yargs.positional('file', { - describe: 'Path to the CTRF report file to validate', - type: 'string', - demandOption: true, - }) - }, - async argv => { - await validateReport(argv.file as string, false) - } - ) - .command( - 'validate-strict ', - 'Strict validation with additionalProperties enforcement', - yargs => { - return yargs.positional('file', { - describe: 'Path to the CTRF report file to validate', - type: 'string', - demandOption: true, - }) - }, - async argv => { - await validateReport(argv.file as string, true) - } - ) - .command( - 'filter ', - 'Filter tests from a CTRF report based on criteria', - yargs => { - return yargs - .positional('file', { - describe: 'Path to the CTRF report file (use - for stdin)', - type: 'string', - demandOption: true, - }) - .option('id', { - describe: 'Filter by test ID (UUID)', - type: 'string', - }) - .option('name', { - describe: 'Filter by test name', - type: 'string', - }) - .option('status', { - describe: - 'Filter by test status (comma-separated: passed,failed,skipped,pending,other)', - type: 'string', - }) - .option('tags', { - describe: 'Filter by tags (comma-separated)', - type: 'string', - }) - .option('suite', { - describe: 'Filter by suite name', - type: 'string', - }) - .option('type', { - describe: 'Filter by test type', - type: 'string', - }) - .option('browser', { - describe: 'Filter by browser (e.g., chrome, firefox)', - type: 'string', - }) - .option('device', { - describe: 'Filter by device', - type: 'string', - }) - .option('flaky', { - describe: 'Filter to flaky tests only', - type: 'boolean', - }) - .option('output', { - alias: 'o', - describe: 'Output file path (optional; defaults to stdout)', - type: 'string', - }) - }, - async argv => { - await filterReport(argv.file as string, { - id: argv.id as string | undefined, - name: argv.name as string | undefined, - status: argv.status as string | undefined, - tags: argv.tags as string | undefined, - suite: argv.suite as string | undefined, - type: argv.type as string | undefined, - browser: argv.browser as string | undefined, - device: argv.device as string | undefined, - flaky: argv.flaky as boolean | undefined, - output: argv.output as string | undefined, - }) - } - ) - .command( - 'generate-test-ids ', - 'Generate deterministic UUIDs for all tests in a report', - yargs => { - return yargs - .positional('file', { - describe: 'Path to the CTRF report file', - type: 'string', - demandOption: true, - }) - .option('output', { - alias: 'o', - describe: 'Output file path (optional; defaults to stdout)', - type: 'string', - }) - }, - async argv => { - await generateTestIds(argv.file as string, { - output: argv.output as string | undefined, - }) - } - ) - .command( - 'generate-report-id ', - 'Generate a unique identifier for the CTRF report', - yargs => { - return yargs - .positional('file', { - describe: 'Path to the CTRF report file', - type: 'string', - demandOption: true, - }) - .option('output', { - alias: 'o', - describe: 'Output file path (optional; defaults to stdout)', - type: 'string', - }) - }, - async argv => { - await generateReportIdCommand(argv.file as string, { - output: argv.output as string | undefined, - }) - } - ) - .command( - 'add-insights ', - 'Analyze historical reports and add insights to current report', - yargs => { - return yargs - .positional('current-report', { - describe: 'Path to the current CTRF report file to enhance', - type: 'string', - demandOption: true, - }) - .positional('historical-reports', { - describe: 'Path to directory containing historical CTRF reports', - type: 'string', - demandOption: true, - }) - .option('output', { - alias: 'o', - describe: - 'Output file path for report with insights (optional; defaults to stdout)', - type: 'string', - }) - }, - async argv => { - await addInsightsCommand( - argv['current-report'] as string, - argv['historical-reports'] as string, - { - output: argv.output as string | undefined, - } - ) - } - ) - .help() - .demandCommand(1, 'You need at least one command before moving on').argv +const _argv = yargs(hideBin(process.argv)) + .command( + "merge ", + "Merge CTRF reports into a single report", + (yargs) => { + return yargs + .positional("directory", { + describe: "Directory of the CTRF reports", + type: "string", + demandOption: true, + }) + .option("output", { + alias: "o", + describe: "Output file path (default: ctrf-report.json)", + type: "string", + default: "ctrf-report.json", + }) + .option("output-dir", { + alias: "d", + describe: "Output directory for merged report", + type: "string", + hidden: true, + deprecated: "Use --output with a path instead", + }) + .option("keep-reports", { + alias: "k", + describe: "Keep existing reports after merging", + type: "boolean", + default: false, + }); + }, + async (argv) => { + await mergeReports( + argv.directory as string, + argv.output as string, + argv["keep-reports"] as boolean, + argv["output-dir"] as string, + ); + }, + ) + .command( + "flaky ", + "Identify flaky tests from a CTRF report file", + (yargs) => { + return yargs.positional("file", { + describe: "CTRF report file", + type: "string", + demandOption: true, + }); + }, + async (argv) => { + await identifyFlakyTests(argv.file as string); + }, + ) + .command( + "validate ", + "Validate a CTRF report against the JSON schema", + (yargs) => { + return yargs.positional("file", { + describe: "Path to the CTRF report file to validate", + type: "string", + demandOption: true, + }); + }, + async (argv) => { + await validateReport(argv.file as string, false); + }, + ) + .command( + "validate-strict ", + "Strict validation with additionalProperties enforcement", + (yargs) => { + return yargs.positional("file", { + describe: "Path to the CTRF report file to validate", + type: "string", + demandOption: true, + }); + }, + async (argv) => { + await validateReport(argv.file as string, true); + }, + ) + .command( + "filter ", + "Filter tests from a CTRF report based on criteria", + (yargs) => { + return yargs + .positional("file", { + describe: "Path to the CTRF report file (use - for stdin)", + type: "string", + demandOption: true, + }) + .option("id", { + describe: "Filter by test ID (UUID)", + type: "string", + }) + .option("name", { + describe: "Filter by test name", + type: "string", + }) + .option("status", { + describe: + "Filter by test status (comma-separated: passed,failed,skipped,pending,other)", + type: "string", + }) + .option("tags", { + describe: "Filter by tags (comma-separated)", + type: "string", + }) + .option("suite", { + describe: "Filter by suite name", + type: "string", + }) + .option("type", { + describe: "Filter by test type", + type: "string", + }) + .option("browser", { + describe: "Filter by browser (e.g., chrome, firefox)", + type: "string", + }) + .option("device", { + describe: "Filter by device", + type: "string", + }) + .option("flaky", { + describe: "Filter to flaky tests only", + type: "boolean", + }) + .option("output", { + alias: "o", + describe: "Output file path (optional; defaults to stdout)", + type: "string", + }); + }, + async (argv) => { + await filterReport(argv.file as string, { + id: argv.id as string | undefined, + name: argv.name as string | undefined, + status: argv.status as string | undefined, + tags: argv.tags as string | undefined, + suite: argv.suite as string | undefined, + type: argv.type as string | undefined, + browser: argv.browser as string | undefined, + device: argv.device as string | undefined, + flaky: argv.flaky as boolean | undefined, + output: argv.output as string | undefined, + }); + }, + ) + .command( + "generate-test-ids ", + "Generate deterministic UUIDs for all tests in a report", + (yargs) => { + return yargs + .positional("file", { + describe: "Path to the CTRF report file", + type: "string", + demandOption: true, + }) + .option("output", { + alias: "o", + describe: "Output file path (optional; defaults to stdout)", + type: "string", + }); + }, + async (argv) => { + await generateTestIds(argv.file as string, { + output: argv.output as string | undefined, + }); + }, + ) + .command( + "generate-report-id ", + "Generate a unique identifier for the CTRF report", + (yargs) => { + return yargs + .positional("file", { + describe: "Path to the CTRF report file", + type: "string", + demandOption: true, + }) + .option("output", { + alias: "o", + describe: "Output file path (optional; defaults to stdout)", + type: "string", + }); + }, + async (argv) => { + await generateReportIdCommand(argv.file as string, { + output: argv.output as string | undefined, + }); + }, + ) + .command( + "add-insights ", + "Analyze historical reports and add insights to current report", + (yargs) => { + return yargs + .positional("current-report", { + describe: "Path to the current CTRF report file to enhance", + type: "string", + demandOption: true, + }) + .positional("historical-reports", { + describe: "Path to directory containing historical CTRF reports", + type: "string", + demandOption: true, + }) + .option("output", { + alias: "o", + describe: + "Output file path for report with insights (optional; defaults to stdout)", + type: "string", + }); + }, + async (argv) => { + await addInsightsCommand( + argv["current-report"] as string, + argv["historical-reports"] as string, + { + output: argv.output as string | undefined, + }, + ); + }, + ) + .help() + .demandCommand(1, "You need at least one command before moving on").argv; diff --git a/src/exit-codes.ts b/src/exit-codes.ts index 091d2e8..3fd8eb2 100644 --- a/src/exit-codes.ts +++ b/src/exit-codes.ts @@ -1,5 +1,5 @@ -export const EXIT_SUCCESS = 0 -export const EXIT_GENERAL_ERROR = 1 -export const EXIT_VALIDATION_FAILED = 2 -export const EXIT_FILE_NOT_FOUND = 3 -export const EXIT_INVALID_CTRF = 4 +export const EXIT_SUCCESS = 0; +export const EXIT_GENERAL_ERROR = 1; +export const EXIT_VALIDATION_FAILED = 2; +export const EXIT_FILE_NOT_FOUND = 3; +export const EXIT_INVALID_CTRF = 4; diff --git a/src/filter.test.ts b/src/filter.test.ts index 97e9ada..46a105f 100644 --- a/src/filter.test.ts +++ b/src/filter.test.ts @@ -1,366 +1,359 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import fs from 'fs' -import path from 'path' -import os from 'os' -import { - ReportBuilder, - TestBuilder, - filterTests, - isCTRFReport, - stringify, - parse, -} from 'ctrf' -import { filterReport } from './filter.js' - -describe('filterReport', () => { - let tmpDir: string - let reportPath: string - let exitSpy: ReturnType - let consoleLogSpy: ReturnType - let consoleErrorSpy: ReturnType - - const sampleReport = new ReportBuilder() - .tool({ name: 'test-tool' }) - .addTest( - new TestBuilder() - .name('test 1') - .status('passed') - .duration(100) - .tags(['smoke']) - .suite(['Unit']) - .browser('chrome') - .build() - ) - .addTest( - new TestBuilder() - .name('test 2') - .status('failed') - .duration(200) - .tags(['regression']) - .suite(['Integration']) - .build() - ) - .addTest( - new TestBuilder() - .name('test 3') - .status('passed') - .duration(150) - .tags(['smoke', 'regression']) - .suite(['Unit']) - .build() - ) - .addTest( - new TestBuilder() - .name('test 4') - .status('failed') - .duration(180) - .tags(['regression']) - .suite(['E2E']) - .device('mobile') - .build() - ) - .addTest( - new TestBuilder() - .name('test 5') - .status('skipped') - .duration(0) - .tags(['flaky']) - .suite(['Unit']) - .flaky(true) - .build() - ) - .build() - - beforeEach(() => { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ctrf-filter-test-')) - reportPath = path.join(tmpDir, 'report.json') - fs.writeFileSync(reportPath, JSON.stringify(sampleReport, null, 2)) - - exitSpy = vi - .spyOn(process, 'exit') - .mockImplementation((() => undefined as never) as any) as any - consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) - consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) - }) - - afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }) - exitSpy.mockRestore() - consoleLogSpy.mockRestore() - consoleErrorSpy.mockRestore() - }) - - describe('status filtering', () => { - it('should filter by single status', async () => { - await filterReport(reportPath, { status: 'failed' }) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.results.tests).toHaveLength(2) - expect( - result.results.tests.every((t: any) => t.status === 'failed') - ).toBe(true) - expect(result.results.summary.failed).toBe(2) - }) - - it('should filter by multiple statuses (OR logic)', async () => { - await filterReport(reportPath, { status: 'passed,failed' }) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.results.tests).toHaveLength(4) - expect( - result.results.tests.every((t: any) => - ['passed', 'failed'].includes(t.status) - ) - ).toBe(true) - }) - }) - - describe('tags filtering', () => { - it('should filter by tag', async () => { - await filterReport(reportPath, { tags: 'smoke' }) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] as string - const result = JSON.parse(output as string) - - expect(result.results.tests).toHaveLength(2) - expect( - result.results.tests.every((t: any) => t.tags?.includes('smoke')) - ).toBe(true) - - expect(isCTRFReport(result)).toBe(true) - }) - }) - - describe('suite filtering', () => { - it('should filter by suite', async () => { - await filterReport(reportPath, { suite: 'Unit' }) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.results.tests).toHaveLength(3) - }) - }) - - describe('flaky filtering', () => { - it('should filter to flaky tests only', async () => { - await filterReport(reportPath, { flaky: true }) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.results.tests).toHaveLength(1) - expect(result.results.tests[0].name).toBe('test 5') - }) - }) - - describe('browser filtering', () => { - it('should filter by browser', async () => { - await filterReport(reportPath, { browser: 'chrome' }) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.results.tests).toHaveLength(1) - expect(result.results.tests[0].name).toBe('test 1') - }) - }) - - describe('device filtering', () => { - it('should filter by device', async () => { - await filterReport(reportPath, { device: 'mobile' }) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.results.tests).toHaveLength(1) - expect(result.results.tests[0].name).toBe('test 4') - }) - }) - - describe('combined filtering (AND logic)', () => { - it('should apply multiple criteria with AND logic', async () => { - await filterReport(reportPath, { status: 'passed', suite: 'Unit' }) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.results.tests).toHaveLength(2) - expect( - result.results.tests.every((t: any) => t.status === 'passed') - ).toBe(true) - }) - }) - - describe('output option', () => { - it('should write to file when --output is specified', async () => { - const outputPath = path.join(tmpDir, 'filtered.json') - - await filterReport(reportPath, { status: 'failed', output: outputPath }) - expect(exitSpy).toHaveBeenCalledWith(0) - - expect(fs.existsSync(outputPath)).toBe(true) - - const savedContent = JSON.parse(fs.readFileSync(outputPath, 'utf-8')) - expect(savedContent.results.tests).toHaveLength(2) - - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('Filtered 2 tests') - ) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('Saved to') - ) - }) - }) - - describe('error handling', () => { - it('should exit with code 3 for file not found', async () => { - const nonExistentPath = path.join(tmpDir, 'nonexistent.json') - await filterReport(nonExistentPath, {}) - expect(exitSpy).toHaveBeenCalledWith(3) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('File not found') - ) - }) - - it('should produce valid CTRF output', async () => { - await filterReport(reportPath, { status: 'failed' }) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.reportFormat).toBe('CTRF') - expect(result.specVersion).toBeDefined() - expect(result.results).toBeDefined() - expect(result.results.tool).toBeDefined() - expect(result.results.summary).toBeDefined() - expect(result.results.tests).toBeDefined() - }) - }) - - describe('stdin input', () => { - it('should read from stdin when filePath is "-"', async () => { - const report = new ReportBuilder() - .tool({ name: 'test-tool' }) - .addTest( - new TestBuilder() - .name('test 1') - .status('passed') - .duration(100) - .build() - ) - .build() - - const reportJson = stringify(report) - - const stdinMock = { - setEncoding: vi.fn(), - on: vi.fn((event: string, callback: Function) => { - if (event === 'data') { - callback(reportJson) - } else if (event === 'end') { - callback() - } - }), - } - - vi.spyOn(process, 'stdin', 'get').mockReturnValue(stdinMock as any) - - await filterReport('-', { name: 'test 1' }) - - expect(exitSpy).toHaveBeenCalledWith(0) - expect(consoleLogSpy).toHaveBeenCalled() - }) - }) - - describe('type filtering', () => { - it('should filter by type when manually specified', async () => { - const report = new ReportBuilder() - .tool({ name: 'test-tool' }) - .addTest( - new TestBuilder() - .name('unit test') - .status('passed') - .duration(100) - .type('unit') - .build() - ) - .addTest( - new TestBuilder() - .name('e2e test') - .status('passed') - .duration(100) - .type('e2e') - .build() - ) - .build() - - fs.writeFileSync(reportPath, stringify(report)) - - await filterReport(reportPath, { type: 'unit' }) - - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.results.tests).toHaveLength(1) - expect(result.results.tests[0].name).toBe('unit test') - expect(result.results.tests[0].type).toBe('unit') - }) - - it('should correctly update summary when type filtering', async () => { - const report = new ReportBuilder() - .tool({ name: 'test-tool' }) - .addTest( - new TestBuilder() - .name('unit 1') - .status('passed') - .duration(100) - .type('unit') - .build() - ) - .addTest( - new TestBuilder() - .name('unit 2') - .status('failed') - .duration(100) - .type('unit') - .build() - ) - .addTest( - new TestBuilder() - .name('e2e test') - .status('passed') - .duration(100) - .type('e2e') - .build() - ) - .build() - - fs.writeFileSync(reportPath, stringify(report)) - - await filterReport(reportPath, { type: 'unit' }) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.results.summary.tests).toBe(2) - expect(result.results.summary.passed).toBe(1) - expect(result.results.summary.failed).toBe(1) - }) - }) -}) +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { ReportBuilder, TestBuilder, isCTRFReport, stringify } from "ctrf"; +import { filterReport } from "./filter.js"; + +describe("filterReport", () => { + let tmpDir: string; + let reportPath: string; + let exitSpy: ReturnType; + let consoleLogSpy: ReturnType; + let consoleErrorSpy: ReturnType; + + const sampleReport = new ReportBuilder() + .tool({ name: "test-tool" }) + .addTest( + new TestBuilder() + .name("test 1") + .status("passed") + .duration(100) + .tags(["smoke"]) + .suite(["Unit"]) + .browser("chrome") + .build(), + ) + .addTest( + new TestBuilder() + .name("test 2") + .status("failed") + .duration(200) + .tags(["regression"]) + .suite(["Integration"]) + .build(), + ) + .addTest( + new TestBuilder() + .name("test 3") + .status("passed") + .duration(150) + .tags(["smoke", "regression"]) + .suite(["Unit"]) + .build(), + ) + .addTest( + new TestBuilder() + .name("test 4") + .status("failed") + .duration(180) + .tags(["regression"]) + .suite(["E2E"]) + .device("mobile") + .build(), + ) + .addTest( + new TestBuilder() + .name("test 5") + .status("skipped") + .duration(0) + .tags(["flaky"]) + .suite(["Unit"]) + .flaky(true) + .build(), + ) + .build(); + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ctrf-filter-test-")); + reportPath = path.join(tmpDir, "report.json"); + fs.writeFileSync(reportPath, JSON.stringify(sampleReport, null, 2)); + + exitSpy = vi + .spyOn(process, "exit") + .mockImplementation((() => undefined as never) as any) as any; + consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + exitSpy.mockRestore(); + consoleLogSpy.mockRestore(); + consoleErrorSpy.mockRestore(); + }); + + describe("status filtering", () => { + it("should filter by single status", async () => { + await filterReport(reportPath, { status: "failed" }); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.results.tests).toHaveLength(2); + expect( + result.results.tests.every((t: any) => t.status === "failed"), + ).toBe(true); + expect(result.results.summary.failed).toBe(2); + }); + + it("should filter by multiple statuses (OR logic)", async () => { + await filterReport(reportPath, { status: "passed,failed" }); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.results.tests).toHaveLength(4); + expect( + result.results.tests.every((t: any) => + ["passed", "failed"].includes(t.status), + ), + ).toBe(true); + }); + }); + + describe("tags filtering", () => { + it("should filter by tag", async () => { + await filterReport(reportPath, { tags: "smoke" }); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0] as string; + const result = JSON.parse(output as string); + + expect(result.results.tests).toHaveLength(2); + expect( + result.results.tests.every((t: any) => t.tags?.includes("smoke")), + ).toBe(true); + + expect(isCTRFReport(result)).toBe(true); + }); + }); + + describe("suite filtering", () => { + it("should filter by suite", async () => { + await filterReport(reportPath, { suite: "Unit" }); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.results.tests).toHaveLength(3); + }); + }); + + describe("flaky filtering", () => { + it("should filter to flaky tests only", async () => { + await filterReport(reportPath, { flaky: true }); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.results.tests).toHaveLength(1); + expect(result.results.tests[0].name).toBe("test 5"); + }); + }); + + describe("browser filtering", () => { + it("should filter by browser", async () => { + await filterReport(reportPath, { browser: "chrome" }); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.results.tests).toHaveLength(1); + expect(result.results.tests[0].name).toBe("test 1"); + }); + }); + + describe("device filtering", () => { + it("should filter by device", async () => { + await filterReport(reportPath, { device: "mobile" }); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.results.tests).toHaveLength(1); + expect(result.results.tests[0].name).toBe("test 4"); + }); + }); + + describe("combined filtering (AND logic)", () => { + it("should apply multiple criteria with AND logic", async () => { + await filterReport(reportPath, { status: "passed", suite: "Unit" }); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.results.tests).toHaveLength(2); + expect( + result.results.tests.every((t: any) => t.status === "passed"), + ).toBe(true); + }); + }); + + describe("output option", () => { + it("should write to file when --output is specified", async () => { + const outputPath = path.join(tmpDir, "filtered.json"); + + await filterReport(reportPath, { status: "failed", output: outputPath }); + expect(exitSpy).toHaveBeenCalledWith(0); + + expect(fs.existsSync(outputPath)).toBe(true); + + const savedContent = JSON.parse(fs.readFileSync(outputPath, "utf-8")); + expect(savedContent.results.tests).toHaveLength(2); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Filtered 2 tests"), + ); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Saved to"), + ); + }); + }); + + describe("error handling", () => { + it("should exit with code 3 for file not found", async () => { + const nonExistentPath = path.join(tmpDir, "nonexistent.json"); + await filterReport(nonExistentPath, {}); + expect(exitSpy).toHaveBeenCalledWith(3); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("File not found"), + ); + }); + + it("should produce valid CTRF output", async () => { + await filterReport(reportPath, { status: "failed" }); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.reportFormat).toBe("CTRF"); + expect(result.specVersion).toBeDefined(); + expect(result.results).toBeDefined(); + expect(result.results.tool).toBeDefined(); + expect(result.results.summary).toBeDefined(); + expect(result.results.tests).toBeDefined(); + }); + }); + + describe("stdin input", () => { + it('should read from stdin when filePath is "-"', async () => { + const report = new ReportBuilder() + .tool({ name: "test-tool" }) + .addTest( + new TestBuilder() + .name("test 1") + .status("passed") + .duration(100) + .build(), + ) + .build(); + + const reportJson = stringify(report); + + const stdinMock = { + setEncoding: vi.fn(), + on: vi.fn((event: string, callback: Function) => { + if (event === "data") { + callback(reportJson); + } else if (event === "end") { + callback(); + } + }), + }; + + vi.spyOn(process, "stdin", "get").mockReturnValue(stdinMock as any); + + await filterReport("-", { name: "test 1" }); + + expect(exitSpy).toHaveBeenCalledWith(0); + expect(consoleLogSpy).toHaveBeenCalled(); + }); + }); + + describe("type filtering", () => { + it("should filter by type when manually specified", async () => { + const report = new ReportBuilder() + .tool({ name: "test-tool" }) + .addTest( + new TestBuilder() + .name("unit test") + .status("passed") + .duration(100) + .type("unit") + .build(), + ) + .addTest( + new TestBuilder() + .name("e2e test") + .status("passed") + .duration(100) + .type("e2e") + .build(), + ) + .build(); + + fs.writeFileSync(reportPath, stringify(report)); + + await filterReport(reportPath, { type: "unit" }); + + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.results.tests).toHaveLength(1); + expect(result.results.tests[0].name).toBe("unit test"); + expect(result.results.tests[0].type).toBe("unit"); + }); + + it("should correctly update summary when type filtering", async () => { + const report = new ReportBuilder() + .tool({ name: "test-tool" }) + .addTest( + new TestBuilder() + .name("unit 1") + .status("passed") + .duration(100) + .type("unit") + .build(), + ) + .addTest( + new TestBuilder() + .name("unit 2") + .status("failed") + .duration(100) + .type("unit") + .build(), + ) + .addTest( + new TestBuilder() + .name("e2e test") + .status("passed") + .duration(100) + .type("e2e") + .build(), + ) + .build(); + + fs.writeFileSync(reportPath, stringify(report)); + + await filterReport(reportPath, { type: "unit" }); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.results.summary.tests).toBe(2); + expect(result.results.summary.passed).toBe(1); + expect(result.results.summary.failed).toBe(1); + }); + }); +}); diff --git a/src/filter.ts b/src/filter.ts index d44b8c4..7c0ab9a 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -1,156 +1,158 @@ -import fs from 'fs' -import path from 'path' +import fs from "node:fs"; +import path from "node:path"; import { - parse, - filterTests, - stringify, - calculateSummary, - CTRFReport, - TestStatus, - FilterCriteria, -} from 'ctrf' + parse, + filterTests, + stringify, + calculateSummary, + type CTRFReport, + type TestStatus, + type FilterCriteria, +} from "ctrf"; import { - EXIT_SUCCESS, - EXIT_GENERAL_ERROR, - EXIT_FILE_NOT_FOUND, - EXIT_INVALID_CTRF, -} from './exit-codes.js' + EXIT_SUCCESS, + EXIT_GENERAL_ERROR, + EXIT_FILE_NOT_FOUND, + EXIT_INVALID_CTRF, +} from "./exit-codes.js"; export interface FilterOptions { - id?: string - name?: string - status?: string - tags?: string - suite?: string - type?: string - browser?: string - device?: string - flaky?: boolean - output?: string + id?: string; + name?: string; + status?: string; + tags?: string; + suite?: string; + type?: string; + browser?: string; + device?: string; + flaky?: boolean; + output?: string; } export async function filterReport( - filePath: string, - options: FilterOptions + filePath: string, + options: FilterOptions, ): Promise { - try { - let fileContent: string - let displayPath: string - - if (filePath === '-') { - fileContent = await readStdin() - displayPath = 'stdin' - } else { - const resolvedPath = path.resolve(filePath) - - if (!fs.existsSync(resolvedPath)) { - console.error(`Error: File not found: ${resolvedPath}`) - process.exit(EXIT_FILE_NOT_FOUND) - } - - fileContent = fs.readFileSync(resolvedPath, 'utf-8') - displayPath = path.basename(filePath) - } - - let report: CTRFReport - try { - report = parse(fileContent) - } catch (parseError) { - console.error( - `Error: Invalid CTRF report - ${(parseError as Error).message}` - ) - process.exit(EXIT_INVALID_CTRF) - } - - const criteria: FilterCriteria = {} - - if (options.id) { - criteria.id = options.id - } - - if (options.name) { - criteria.name = options.name - } - - if (options.status) { - const statuses = options.status - .split(',') - .map(s => s.trim() as TestStatus) - criteria.status = statuses - } - - if (options.tags) { - const tags = options.tags.split(',').map(t => t.trim()) - criteria.tags = tags - } - - if (options.suite) { - criteria.suite = options.suite - } - - if (options.browser) { - criteria.browser = options.browser - } - - if (options.device) { - criteria.device = options.device - } - - if (options.flaky !== undefined) { - criteria.flaky = options.flaky - } - - let filteredTests = filterTests(report, criteria) - - if (options.type) { - filteredTests = filteredTests.filter(test => test.type === options.type) - } - - const filteredReport: CTRFReport = { - ...report, - results: { - ...report.results, - tests: filteredTests, - summary: calculateSummary(filteredTests), - }, - } - - const output = stringify(filteredReport) - - if (options.output) { - const outputPath = path.resolve(options.output) - const outputDir = path.dirname(outputPath) - - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }) - } - - fs.writeFileSync(outputPath, output, 'utf-8') - console.error( - `✓ Filtered ${filteredTests.length} tests from ${displayPath}` - ) - console.error(`✓ Saved to ${options.output}`) - process.exit(EXIT_SUCCESS) - } else { - console.log(output) - process.exit(EXIT_SUCCESS) - } - } catch (error) { - console.error(`Error: ${(error as Error).message}`) - process.exit(EXIT_GENERAL_ERROR) - } + try { + let fileContent: string; + let displayPath: string; + + if (filePath === "-") { + fileContent = await readStdin(); + displayPath = "stdin"; + } else { + const resolvedPath = path.resolve(filePath); + + if (!fs.existsSync(resolvedPath)) { + console.error(`Error: File not found: ${resolvedPath}`); + process.exit(EXIT_FILE_NOT_FOUND); + } + + fileContent = fs.readFileSync(resolvedPath, "utf-8"); + displayPath = path.basename(filePath); + } + + let report: CTRFReport; + try { + report = parse(fileContent); + } catch (parseError) { + console.error( + `Error: Invalid CTRF report - ${(parseError as Error).message}`, + ); + process.exit(EXIT_INVALID_CTRF); + } + + const criteria: FilterCriteria = {}; + + if (options.id) { + criteria.id = options.id; + } + + if (options.name) { + criteria.name = options.name; + } + + if (options.status) { + const statuses = options.status + .split(",") + .map((s) => s.trim() as TestStatus); + criteria.status = statuses; + } + + if (options.tags) { + const tags = options.tags.split(",").map((t) => t.trim()); + criteria.tags = tags; + } + + if (options.suite) { + criteria.suite = options.suite; + } + + if (options.browser) { + criteria.browser = options.browser; + } + + if (options.device) { + criteria.device = options.device; + } + + if (options.flaky !== undefined) { + criteria.flaky = options.flaky; + } + + let filteredTests = filterTests(report, criteria); + + if (options.type) { + filteredTests = filteredTests.filter( + (test) => test.type === options.type, + ); + } + + const filteredReport: CTRFReport = { + ...report, + results: { + ...report.results, + tests: filteredTests, + summary: calculateSummary(filteredTests), + }, + }; + + const output = stringify(filteredReport); + + if (options.output) { + const outputPath = path.resolve(options.output); + const outputDir = path.dirname(outputPath); + + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + fs.writeFileSync(outputPath, output, "utf-8"); + console.error( + `✓ Filtered ${filteredTests.length} tests from ${displayPath}`, + ); + console.error(`✓ Saved to ${options.output}`); + process.exit(EXIT_SUCCESS); + } else { + console.log(output); + process.exit(EXIT_SUCCESS); + } + } catch (error) { + console.error(`Error: ${(error as Error).message}`); + process.exit(EXIT_GENERAL_ERROR); + } } function readStdin(): Promise { - return new Promise((resolve, reject) => { - let data = '' - process.stdin.setEncoding('utf-8') - process.stdin.on('data', chunk => { - data += chunk - }) - process.stdin.on('end', () => { - resolve(data) - }) - process.stdin.on('error', reject) - }) + return new Promise((resolve, reject) => { + let data = ""; + process.stdin.setEncoding("utf-8"); + process.stdin.on("data", (chunk) => { + data += chunk; + }); + process.stdin.on("end", () => { + resolve(data); + }); + process.stdin.on("error", reject); + }); } diff --git a/src/flaky.test.ts b/src/flaky.test.ts index 0bbb04a..21238b3 100644 --- a/src/flaky.test.ts +++ b/src/flaky.test.ts @@ -1,149 +1,149 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import fs from 'fs' -import path from 'path' -import os from 'os' -import { ReportBuilder, TestBuilder } from 'ctrf' -import { identifyFlakyTests } from './flaky.js' - -describe('identifyFlakyTests', () => { - let tmpDir: string - let reportPath: string - let consoleLogSpy: ReturnType - let consoleErrorSpy: ReturnType - - beforeEach(() => { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ctrf-flaky-test-')) - reportPath = path.join(tmpDir, 'report.json') - consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) - consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) - }) - - afterEach(() => { - vi.restoreAllMocks() - if (fs.existsSync(tmpDir)) { - fs.rmSync(tmpDir, { recursive: true }) - } - }) - - it('should identify flaky tests', async () => { - const report = new ReportBuilder() - .tool({ name: 'test-tool' }) - .addTest( - new TestBuilder() - .name('flaky test 1') - .status('passed') - .duration(100) - .flaky(true) - .retries(2) - .build() - ) - .addTest( - new TestBuilder() - .name('flaky test 2') - .status('passed') - .duration(100) - .flaky(true) - .retries(1) - .build() - ) - .addTest( - new TestBuilder() - .name('stable test') - .status('passed') - .duration(100) - .build() - ) - .build() - - fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)) - - await identifyFlakyTests(reportPath) - - expect(consoleLogSpy).toHaveBeenCalledWith('Found 2 flaky test(s):') - expect(consoleLogSpy).toHaveBeenCalledWith( - '- Test Name: flaky test 1, Retries: 2' - ) - expect(consoleLogSpy).toHaveBeenCalledWith( - '- Test Name: flaky test 2, Retries: 1' - ) - }) - - it('should report when no flaky tests found', async () => { - const report = new ReportBuilder() - .tool({ name: 'test-tool' }) - .addTest( - new TestBuilder() - .name('stable test') - .status('passed') - .duration(100) - .build() - ) - .build() - - fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)) - - await identifyFlakyTests(reportPath) - - expect(consoleLogSpy).toHaveBeenCalledWith( - expect.stringContaining('No flaky tests found') - ) - }) - - it('should handle file not found', async () => { - const nonExistentPath = path.join(tmpDir, 'non-existent.json') - - await identifyFlakyTests(nonExistentPath) - - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('does not exist') - ) - }) - - it('should handle invalid JSON', async () => { - fs.writeFileSync(reportPath, 'invalid json {') - - await identifyFlakyTests(reportPath) - - expect(consoleErrorSpy).toHaveBeenCalledWith( - 'Error identifying flaky tests:', - expect.any(Error) - ) - }) - - it('should handle missing results property', async () => { - fs.writeFileSync( - reportPath, - JSON.stringify({ invalid: 'structure' }, null, 2) - ) - - await identifyFlakyTests(reportPath) - - expect(consoleErrorSpy).toHaveBeenCalledWith( - 'Error identifying flaky tests:', - expect.any(Error) - ) - }) - - it('should display test retries correctly', async () => { - const report = new ReportBuilder() - .tool({ name: 'test-tool' }) - .addTest( - new TestBuilder() - .name('flaky with retries') - .status('passed') - .duration(100) - .flaky(true) - .retries(5) - .build() - ) - .build() - - fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)) - - await identifyFlakyTests(reportPath) - - expect(consoleLogSpy).toHaveBeenCalledWith( - '- Test Name: flaky with retries, Retries: 5' - ) - }) -}) +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { ReportBuilder, TestBuilder } from "ctrf"; +import { identifyFlakyTests } from "./flaky.js"; + +describe("identifyFlakyTests", () => { + let tmpDir: string; + let reportPath: string; + let consoleLogSpy: ReturnType; + let consoleErrorSpy: ReturnType; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ctrf-flaky-test-")); + reportPath = path.join(tmpDir, "report.json"); + consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + if (fs.existsSync(tmpDir)) { + fs.rmSync(tmpDir, { recursive: true }); + } + }); + + it("should identify flaky tests", async () => { + const report = new ReportBuilder() + .tool({ name: "test-tool" }) + .addTest( + new TestBuilder() + .name("flaky test 1") + .status("passed") + .duration(100) + .flaky(true) + .retries(2) + .build(), + ) + .addTest( + new TestBuilder() + .name("flaky test 2") + .status("passed") + .duration(100) + .flaky(true) + .retries(1) + .build(), + ) + .addTest( + new TestBuilder() + .name("stable test") + .status("passed") + .duration(100) + .build(), + ) + .build(); + + fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); + + await identifyFlakyTests(reportPath); + + expect(consoleLogSpy).toHaveBeenCalledWith("Found 2 flaky test(s):"); + expect(consoleLogSpy).toHaveBeenCalledWith( + "- Test Name: flaky test 1, Retries: 2", + ); + expect(consoleLogSpy).toHaveBeenCalledWith( + "- Test Name: flaky test 2, Retries: 1", + ); + }); + + it("should report when no flaky tests found", async () => { + const report = new ReportBuilder() + .tool({ name: "test-tool" }) + .addTest( + new TestBuilder() + .name("stable test") + .status("passed") + .duration(100) + .build(), + ) + .build(); + + fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); + + await identifyFlakyTests(reportPath); + + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringContaining("No flaky tests found"), + ); + }); + + it("should handle file not found", async () => { + const nonExistentPath = path.join(tmpDir, "non-existent.json"); + + await identifyFlakyTests(nonExistentPath); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("does not exist"), + ); + }); + + it("should handle invalid JSON", async () => { + fs.writeFileSync(reportPath, "invalid json {"); + + await identifyFlakyTests(reportPath); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + "Error identifying flaky tests:", + expect.any(Error), + ); + }); + + it("should handle missing results property", async () => { + fs.writeFileSync( + reportPath, + JSON.stringify({ invalid: "structure" }, null, 2), + ); + + await identifyFlakyTests(reportPath); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + "Error identifying flaky tests:", + expect.any(Error), + ); + }); + + it("should display test retries correctly", async () => { + const report = new ReportBuilder() + .tool({ name: "test-tool" }) + .addTest( + new TestBuilder() + .name("flaky with retries") + .status("passed") + .duration(100) + .flaky(true) + .retries(5) + .build(), + ) + .build(); + + fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); + + await identifyFlakyTests(reportPath); + + expect(consoleLogSpy).toHaveBeenCalledWith( + "- Test Name: flaky with retries, Retries: 5", + ); + }); +}); diff --git a/src/flaky.ts b/src/flaky.ts index a15f925..8bca5ee 100644 --- a/src/flaky.ts +++ b/src/flaky.ts @@ -1,31 +1,31 @@ -import fs from 'fs' -import path from 'path' +import fs from "node:fs"; +import path from "node:path"; export async function identifyFlakyTests(filePath: string) { - try { - const resolvedFilePath = path.resolve(filePath) + try { + const resolvedFilePath = path.resolve(filePath); - if (!fs.existsSync(resolvedFilePath)) { - console.error(`The file ${resolvedFilePath} does not exist.`) - return - } + if (!fs.existsSync(resolvedFilePath)) { + console.error(`The file ${resolvedFilePath} does not exist.`); + return; + } - const fileContent = fs.readFileSync(resolvedFilePath, 'utf8') - const report = JSON.parse(fileContent) + const fileContent = fs.readFileSync(resolvedFilePath, "utf8"); + const report = JSON.parse(fileContent); - const flakyTests = report.results.tests.filter( - (test: { flaky?: boolean }) => test.flaky === true - ) + const flakyTests = report.results.tests.filter( + (test: { flaky?: boolean }) => test.flaky === true, + ); - if (flakyTests.length > 0) { - console.log(`Found ${flakyTests.length} flaky test(s):`) - flakyTests.forEach((test: { name: string; retries?: number }) => { - console.log(`- Test Name: ${test.name}, Retries: ${test.retries}`) - }) - } else { - console.log(`No flaky tests found in ${resolvedFilePath}.`) - } - } catch (error) { - console.error('Error identifying flaky tests:', error) - } + if (flakyTests.length > 0) { + console.log(`Found ${flakyTests.length} flaky test(s):`); + flakyTests.forEach((test: { name: string; retries?: number }) => { + console.log(`- Test Name: ${test.name}, Retries: ${test.retries}`); + }); + } else { + console.log(`No flaky tests found in ${resolvedFilePath}.`); + } + } catch (error) { + console.error("Error identifying flaky tests:", error); + } } diff --git a/src/generate-report-id.test.ts b/src/generate-report-id.test.ts index a3e8dd3..1e7e367 100644 --- a/src/generate-report-id.test.ts +++ b/src/generate-report-id.test.ts @@ -1,163 +1,157 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import fs from 'fs' -import path from 'path' -import os from 'os' -import { - ReportBuilder, - TestBuilder, - generateReportId, - stringify, - parse, -} from 'ctrf' -import { generateReportIdCommand } from './generate-report-id.js' - -describe('generateReportIdCommand', () => { - let tmpDir: string - let reportPath: string - let exitSpy: ReturnType - let consoleLogSpy: ReturnType - let consoleErrorSpy: ReturnType - - const sampleReport = new ReportBuilder() - .tool({ name: 'test-tool' }) - .addTest( - new TestBuilder().name('test 1').status('passed').duration(100).build() - ) - .addTest( - new TestBuilder().name('test 2').status('failed').duration(200).build() - ) - .build() - - beforeEach(() => { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ctrf-report-id-test-')) - reportPath = path.join(tmpDir, 'report.json') - fs.writeFileSync(reportPath, JSON.stringify(sampleReport, null, 2)) - - exitSpy = vi - .spyOn(process, 'exit') - .mockImplementation((() => undefined as never) as any) as any - consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) - consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) - }) - - afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }) - exitSpy.mockRestore() - consoleLogSpy.mockRestore() - consoleErrorSpy.mockRestore() - }) - - describe('report ID generation', () => { - it('should add reportId to the report', async () => { - await generateReportIdCommand(reportPath) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.reportId).toBeDefined() - }) - - it('should generate a UUID', async () => { - await generateReportIdCommand(reportPath) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] as string - const result = JSON.parse(output as string) - - const uuidRegex = - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i - expect(uuidRegex.test(result.reportId)).toBe(true) - }) - - it('should generate unique IDs on each call', async () => { - await generateReportIdCommand(reportPath) - const output1 = JSON.parse(consoleLogSpy.mock.calls[0][0] as string) - - consoleLogSpy.mockClear() - await generateReportIdCommand(reportPath) - const output2 = JSON.parse(consoleLogSpy.mock.calls[0][0] as string) - - expect(output1.reportId).not.toBe(output2.reportId) - }) - - it('should replace existing reportId', async () => { - const reportWithId = { ...sampleReport, reportId: 'existing-id' } - const pathWithId = path.join(tmpDir, 'report-with-id.json') - fs.writeFileSync(pathWithId, JSON.stringify(reportWithId, null, 2)) - - await generateReportIdCommand(pathWithId) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.reportId).not.toBe('existing-id') - }) - }) - - describe('output option', () => { - it('should write to file when --output is specified', async () => { - const outputPath = path.join(tmpDir, 'with-id.json') - - await generateReportIdCommand(reportPath, { output: outputPath }) - expect(exitSpy).toHaveBeenCalledWith(0) - - expect(fs.existsSync(outputPath)).toBe(true) - - const savedContent = JSON.parse(fs.readFileSync(outputPath, 'utf-8')) - expect(savedContent.reportId).toBeDefined() - - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('Generated report ID') - ) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('Saved to') - ) - }) - - it('should print to stdout when no --output specified', async () => { - await generateReportIdCommand(reportPath) - expect(exitSpy).toHaveBeenCalledWith(0) - - expect(consoleLogSpy).toHaveBeenCalled() - const output = consoleLogSpy.mock.calls[0][0] - expect(() => JSON.parse(output as string)).not.toThrow() - }) - }) - - describe('error handling', () => { - it('should exit with code 3 for file not found', async () => { - const nonExistentPath = path.join(tmpDir, 'nonexistent.json') - await generateReportIdCommand(nonExistentPath) - expect(exitSpy).toHaveBeenCalledWith(3) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('File not found') - ) - }) - - it('should exit with code 4 for invalid JSON', async () => { - const invalidJsonPath = path.join(tmpDir, 'invalid.json') - fs.writeFileSync(invalidJsonPath, 'not valid json') - await generateReportIdCommand(invalidJsonPath) - expect(exitSpy).toHaveBeenCalledWith(4) - }) - }) - - describe('output validity', () => { - it('should produce valid CTRF output', async () => { - await generateReportIdCommand(reportPath) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.reportFormat).toBe('CTRF') - expect(result.specVersion).toBeDefined() - expect(result.results).toBeDefined() - expect(result.results.tool).toBeDefined() - expect(result.results.summary).toBeDefined() - expect(result.results.tests).toBeDefined() - }) - }) -}) +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { ReportBuilder, TestBuilder } from "ctrf"; +import { generateReportIdCommand } from "./generate-report-id.js"; + +describe("generateReportIdCommand", () => { + let tmpDir: string; + let reportPath: string; + let exitSpy: ReturnType; + let consoleLogSpy: ReturnType; + let consoleErrorSpy: ReturnType; + + const sampleReport = new ReportBuilder() + .tool({ name: "test-tool" }) + .addTest( + new TestBuilder().name("test 1").status("passed").duration(100).build(), + ) + .addTest( + new TestBuilder().name("test 2").status("failed").duration(200).build(), + ) + .build(); + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ctrf-report-id-test-")); + reportPath = path.join(tmpDir, "report.json"); + fs.writeFileSync(reportPath, JSON.stringify(sampleReport, null, 2)); + + exitSpy = vi + .spyOn(process, "exit") + .mockImplementation((() => undefined as never) as any) as any; + consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + exitSpy.mockRestore(); + consoleLogSpy.mockRestore(); + consoleErrorSpy.mockRestore(); + }); + + describe("report ID generation", () => { + it("should add reportId to the report", async () => { + await generateReportIdCommand(reportPath); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.reportId).toBeDefined(); + }); + + it("should generate a UUID", async () => { + await generateReportIdCommand(reportPath); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0] as string; + const result = JSON.parse(output as string); + + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + expect(uuidRegex.test(result.reportId)).toBe(true); + }); + + it("should generate unique IDs on each call", async () => { + await generateReportIdCommand(reportPath); + const output1 = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); + + consoleLogSpy.mockClear(); + await generateReportIdCommand(reportPath); + const output2 = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); + + expect(output1.reportId).not.toBe(output2.reportId); + }); + + it("should replace existing reportId", async () => { + const reportWithId = { ...sampleReport, reportId: "existing-id" }; + const pathWithId = path.join(tmpDir, "report-with-id.json"); + fs.writeFileSync(pathWithId, JSON.stringify(reportWithId, null, 2)); + + await generateReportIdCommand(pathWithId); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.reportId).not.toBe("existing-id"); + }); + }); + + describe("output option", () => { + it("should write to file when --output is specified", async () => { + const outputPath = path.join(tmpDir, "with-id.json"); + + await generateReportIdCommand(reportPath, { output: outputPath }); + expect(exitSpy).toHaveBeenCalledWith(0); + + expect(fs.existsSync(outputPath)).toBe(true); + + const savedContent = JSON.parse(fs.readFileSync(outputPath, "utf-8")); + expect(savedContent.reportId).toBeDefined(); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Generated report ID"), + ); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Saved to"), + ); + }); + + it("should print to stdout when no --output specified", async () => { + await generateReportIdCommand(reportPath); + expect(exitSpy).toHaveBeenCalledWith(0); + + expect(consoleLogSpy).toHaveBeenCalled(); + const output = consoleLogSpy.mock.calls[0][0]; + expect(() => JSON.parse(output as string)).not.toThrow(); + }); + }); + + describe("error handling", () => { + it("should exit with code 3 for file not found", async () => { + const nonExistentPath = path.join(tmpDir, "nonexistent.json"); + await generateReportIdCommand(nonExistentPath); + expect(exitSpy).toHaveBeenCalledWith(3); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("File not found"), + ); + }); + + it("should exit with code 4 for invalid JSON", async () => { + const invalidJsonPath = path.join(tmpDir, "invalid.json"); + fs.writeFileSync(invalidJsonPath, "not valid json"); + await generateReportIdCommand(invalidJsonPath); + expect(exitSpy).toHaveBeenCalledWith(4); + }); + }); + + describe("output validity", () => { + it("should produce valid CTRF output", async () => { + await generateReportIdCommand(reportPath); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.reportFormat).toBe("CTRF"); + expect(result.specVersion).toBeDefined(); + expect(result.results).toBeDefined(); + expect(result.results.tool).toBeDefined(); + expect(result.results.summary).toBeDefined(); + expect(result.results.tests).toBeDefined(); + }); + }); +}); diff --git a/src/generate-report-id.ts b/src/generate-report-id.ts index 8099dea..d1e78d1 100644 --- a/src/generate-report-id.ts +++ b/src/generate-report-id.ts @@ -1,67 +1,67 @@ -import fs from 'fs' -import path from 'path' -import { parse, generateReportId, stringify, CTRFReport } from 'ctrf' +import fs from "node:fs"; +import path from "node:path"; +import { parse, generateReportId, stringify, type CTRFReport } from "ctrf"; -const EXIT_SUCCESS = 0 -const EXIT_GENERAL_ERROR = 1 -const EXIT_FILE_NOT_FOUND = 3 -const EXIT_INVALID_CTRF = 4 +const EXIT_SUCCESS = 0; +const EXIT_GENERAL_ERROR = 1; +const EXIT_FILE_NOT_FOUND = 3; +const EXIT_INVALID_CTRF = 4; export interface GenerateReportIdOptions { - output?: string + output?: string; } export async function generateReportIdCommand( - filePath: string, - options: GenerateReportIdOptions = {} + filePath: string, + options: GenerateReportIdOptions = {}, ): Promise { - try { - const resolvedPath = path.resolve(filePath) + try { + const resolvedPath = path.resolve(filePath); - if (!fs.existsSync(resolvedPath)) { - console.error(`Error: File not found: ${resolvedPath}`) - process.exit(EXIT_FILE_NOT_FOUND) - } + if (!fs.existsSync(resolvedPath)) { + console.error(`Error: File not found: ${resolvedPath}`); + process.exit(EXIT_FILE_NOT_FOUND); + } - const fileContent = fs.readFileSync(resolvedPath, 'utf-8') + const fileContent = fs.readFileSync(resolvedPath, "utf-8"); - let report: CTRFReport - try { - report = parse(fileContent) - } catch (parseError) { - console.error( - `Error: Invalid CTRF report - ${(parseError as Error).message}` - ) - process.exit(EXIT_INVALID_CTRF) - } + let report: CTRFReport; + try { + report = parse(fileContent); + } catch (parseError) { + console.error( + `Error: Invalid CTRF report - ${(parseError as Error).message}`, + ); + process.exit(EXIT_INVALID_CTRF); + } - const reportId = generateReportId() + const reportId = generateReportId(); - const updatedReport: CTRFReport = { - ...report, - reportId, - } + const updatedReport: CTRFReport = { + ...report, + reportId, + }; - const output = stringify(updatedReport) + const output = stringify(updatedReport); - if (options.output) { - const outputPath = path.resolve(options.output) - const outputDir = path.dirname(outputPath) + if (options.output) { + const outputPath = path.resolve(options.output); + const outputDir = path.dirname(outputPath); - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }) - } + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } - fs.writeFileSync(outputPath, output, 'utf-8') - console.error(`✓ Generated report ID: ${reportId}`) - console.error(`✓ Saved to ${options.output}`) - process.exit(EXIT_SUCCESS) - } else { - console.log(output) - process.exit(EXIT_SUCCESS) - } - } catch (error) { - console.error(`Error: ${(error as Error).message}`) - process.exit(EXIT_GENERAL_ERROR) - } + fs.writeFileSync(outputPath, output, "utf-8"); + console.error(`✓ Generated report ID: ${reportId}`); + console.error(`✓ Saved to ${options.output}`); + process.exit(EXIT_SUCCESS); + } else { + console.log(output); + process.exit(EXIT_SUCCESS); + } + } catch (error) { + console.error(`Error: ${(error as Error).message}`); + process.exit(EXIT_GENERAL_ERROR); + } } diff --git a/src/generate-test-ids.test.ts b/src/generate-test-ids.test.ts index 4a8f3e1..f3c7bd4 100644 --- a/src/generate-test-ids.test.ts +++ b/src/generate-test-ids.test.ts @@ -1,157 +1,151 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import fs from 'fs' -import path from 'path' -import os from 'os' -import { - ReportBuilder, - TestBuilder, - generateTestId, - stringify, - parse, -} from 'ctrf' -import { generateTestIds } from './generate-test-ids.js' - -describe('generateTestIds', () => { - let tmpDir: string - let reportPath: string - let exitSpy: ReturnType - let consoleLogSpy: ReturnType - let consoleErrorSpy: ReturnType - - const sampleReport = new ReportBuilder() - .tool({ name: 'test-tool' }) - .addTest( - new TestBuilder().name('test 1').status('passed').duration(100).build() - ) - .addTest( - new TestBuilder().name('test 2').status('failed').duration(200).build() - ) - .addTest( - new TestBuilder().name('test 3').status('passed').duration(150).build() - ) - .build() - - beforeEach(() => { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ctrf-generate-ids-test-')) - reportPath = path.join(tmpDir, 'report.json') - fs.writeFileSync(reportPath, JSON.stringify(sampleReport, null, 2)) - - exitSpy = vi - .spyOn(process, 'exit') - .mockImplementation((() => undefined as never) as any) as any - consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) - consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) - }) - - afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }) - exitSpy.mockRestore() - consoleLogSpy.mockRestore() - consoleErrorSpy.mockRestore() - }) - - describe('ID generation', () => { - it('should generate IDs for all tests', async () => { - await generateTestIds(reportPath) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] as string - const result = JSON.parse(output as string) - - expect(result.results.tests).toHaveLength(3) - expect(result.results.tests.every((t: any) => t.id)).toBe(true) - }) - - it('should generate deterministic IDs (same input = same ID)', async () => { - await generateTestIds(reportPath) - const output1 = JSON.parse(consoleLogSpy.mock.calls[0][0] as string) - - consoleLogSpy.mockClear() - await generateTestIds(reportPath) - const output2 = JSON.parse(consoleLogSpy.mock.calls[0][0] as string) - - expect(output1.results.tests[0].id).toBe(output2.results.tests[0].id) - expect(output1.results.tests[1].id).toBe(output2.results.tests[1].id) - expect(output1.results.tests[2].id).toBe(output2.results.tests[2].id) - }) - - it('should generate UUIDs', async () => { - await generateTestIds(reportPath) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - const uuidRegex = - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i - result.results.tests.forEach((test: any) => { - expect(test.id).toMatch(uuidRegex) - }) - }) - }) - - describe('output option', () => { - it('should write to file when --output is specified', async () => { - const outputPath = path.join(tmpDir, 'with-ids.json') - - await generateTestIds(reportPath, { output: outputPath }) - expect(exitSpy).toHaveBeenCalledWith(0) - - expect(fs.existsSync(outputPath)).toBe(true) - - const savedContent = JSON.parse(fs.readFileSync(outputPath, 'utf-8')) - expect(savedContent.results.tests.every((t: any) => t.id)).toBe(true) - - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('Generated IDs for 3 tests') - ) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('Saved to') - ) - }) - - it('should print to stdout when no --output specified', async () => { - await generateTestIds(reportPath) - expect(exitSpy).toHaveBeenCalledWith(0) - - expect(consoleLogSpy).toHaveBeenCalled() - const output = consoleLogSpy.mock.calls[0][0] - expect(() => JSON.parse(output as string)).not.toThrow() - }) - }) - - describe('error handling', () => { - it('should exit with code 3 for file not found', async () => { - const nonExistentPath = path.join(tmpDir, 'nonexistent.json') - await generateTestIds(nonExistentPath) - expect(exitSpy).toHaveBeenCalledWith(3) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('File not found') - ) - }) - - it('should exit with code 4 for invalid JSON', async () => { - const invalidJsonPath = path.join(tmpDir, 'invalid.json') - fs.writeFileSync(invalidJsonPath, 'not valid json') - await generateTestIds(invalidJsonPath) - expect(exitSpy).toHaveBeenCalledWith(4) - }) - }) - - describe('output validity', () => { - it('should produce valid CTRF output', async () => { - await generateTestIds(reportPath) - expect(exitSpy).toHaveBeenCalledWith(0) - - const output = consoleLogSpy.mock.calls[0][0] - const result = JSON.parse(output as string) - - expect(result.reportFormat).toBe('CTRF') - expect(result.specVersion).toBeDefined() - expect(result.results).toBeDefined() - expect(result.results.tool).toBeDefined() - expect(result.results.summary).toBeDefined() - expect(result.results.tests).toBeDefined() - }) - }) -}) +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { ReportBuilder, TestBuilder } from "ctrf"; +import { generateTestIds } from "./generate-test-ids.js"; + +describe("generateTestIds", () => { + let tmpDir: string; + let reportPath: string; + let exitSpy: ReturnType; + let consoleLogSpy: ReturnType; + let consoleErrorSpy: ReturnType; + + const sampleReport = new ReportBuilder() + .tool({ name: "test-tool" }) + .addTest( + new TestBuilder().name("test 1").status("passed").duration(100).build(), + ) + .addTest( + new TestBuilder().name("test 2").status("failed").duration(200).build(), + ) + .addTest( + new TestBuilder().name("test 3").status("passed").duration(150).build(), + ) + .build(); + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ctrf-generate-ids-test-")); + reportPath = path.join(tmpDir, "report.json"); + fs.writeFileSync(reportPath, JSON.stringify(sampleReport, null, 2)); + + exitSpy = vi + .spyOn(process, "exit") + .mockImplementation((() => undefined as never) as any) as any; + consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + exitSpy.mockRestore(); + consoleLogSpy.mockRestore(); + consoleErrorSpy.mockRestore(); + }); + + describe("ID generation", () => { + it("should generate IDs for all tests", async () => { + await generateTestIds(reportPath); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0] as string; + const result = JSON.parse(output as string); + + expect(result.results.tests).toHaveLength(3); + expect(result.results.tests.every((t: any) => t.id)).toBe(true); + }); + + it("should generate deterministic IDs (same input = same ID)", async () => { + await generateTestIds(reportPath); + const output1 = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); + + consoleLogSpy.mockClear(); + await generateTestIds(reportPath); + const output2 = JSON.parse(consoleLogSpy.mock.calls[0][0] as string); + + expect(output1.results.tests[0].id).toBe(output2.results.tests[0].id); + expect(output1.results.tests[1].id).toBe(output2.results.tests[1].id); + expect(output1.results.tests[2].id).toBe(output2.results.tests[2].id); + }); + + it("should generate UUIDs", async () => { + await generateTestIds(reportPath); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + result.results.tests.forEach((test: any) => { + expect(test.id).toMatch(uuidRegex); + }); + }); + }); + + describe("output option", () => { + it("should write to file when --output is specified", async () => { + const outputPath = path.join(tmpDir, "with-ids.json"); + + await generateTestIds(reportPath, { output: outputPath }); + expect(exitSpy).toHaveBeenCalledWith(0); + + expect(fs.existsSync(outputPath)).toBe(true); + + const savedContent = JSON.parse(fs.readFileSync(outputPath, "utf-8")); + expect(savedContent.results.tests.every((t: any) => t.id)).toBe(true); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Generated IDs for 3 tests"), + ); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Saved to"), + ); + }); + + it("should print to stdout when no --output specified", async () => { + await generateTestIds(reportPath); + expect(exitSpy).toHaveBeenCalledWith(0); + + expect(consoleLogSpy).toHaveBeenCalled(); + const output = consoleLogSpy.mock.calls[0][0]; + expect(() => JSON.parse(output as string)).not.toThrow(); + }); + }); + + describe("error handling", () => { + it("should exit with code 3 for file not found", async () => { + const nonExistentPath = path.join(tmpDir, "nonexistent.json"); + await generateTestIds(nonExistentPath); + expect(exitSpy).toHaveBeenCalledWith(3); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("File not found"), + ); + }); + + it("should exit with code 4 for invalid JSON", async () => { + const invalidJsonPath = path.join(tmpDir, "invalid.json"); + fs.writeFileSync(invalidJsonPath, "not valid json"); + await generateTestIds(invalidJsonPath); + expect(exitSpy).toHaveBeenCalledWith(4); + }); + }); + + describe("output validity", () => { + it("should produce valid CTRF output", async () => { + await generateTestIds(reportPath); + expect(exitSpy).toHaveBeenCalledWith(0); + + const output = consoleLogSpy.mock.calls[0][0]; + const result = JSON.parse(output as string); + + expect(result.reportFormat).toBe("CTRF"); + expect(result.specVersion).toBeDefined(); + expect(result.results).toBeDefined(); + expect(result.results.tool).toBeDefined(); + expect(result.results.summary).toBeDefined(); + expect(result.results.tests).toBeDefined(); + }); + }); +}); diff --git a/src/generate-test-ids.ts b/src/generate-test-ids.ts index 886a644..300b5c7 100644 --- a/src/generate-test-ids.ts +++ b/src/generate-test-ids.ts @@ -1,73 +1,79 @@ -import fs from 'fs' -import path from 'path' -import { parse, generateTestId, stringify, CTRFReport, Test } from 'ctrf' +import fs from "node:fs"; +import path from "node:path"; +import { + parse, + generateTestId, + stringify, + type CTRFReport, + type Test, +} from "ctrf"; -const EXIT_SUCCESS = 0 -const EXIT_GENERAL_ERROR = 1 -const EXIT_FILE_NOT_FOUND = 3 -const EXIT_INVALID_CTRF = 4 +const EXIT_SUCCESS = 0; +const EXIT_GENERAL_ERROR = 1; +const EXIT_FILE_NOT_FOUND = 3; +const EXIT_INVALID_CTRF = 4; export interface GenerateTestIdsOptions { - output?: string + output?: string; } export async function generateTestIds( - filePath: string, - options: GenerateTestIdsOptions = {} + filePath: string, + options: GenerateTestIdsOptions = {}, ): Promise { - try { - const resolvedPath = path.resolve(filePath) + try { + const resolvedPath = path.resolve(filePath); - if (!fs.existsSync(resolvedPath)) { - console.error(`Error: File not found: ${resolvedPath}`) - process.exit(EXIT_FILE_NOT_FOUND) - } + if (!fs.existsSync(resolvedPath)) { + console.error(`Error: File not found: ${resolvedPath}`); + process.exit(EXIT_FILE_NOT_FOUND); + } - const fileContent = fs.readFileSync(resolvedPath, 'utf-8') + const fileContent = fs.readFileSync(resolvedPath, "utf-8"); - let report: CTRFReport - try { - report = parse(fileContent) - } catch (parseError) { - console.error( - `Error: Invalid CTRF report - ${(parseError as Error).message}` - ) - process.exit(EXIT_INVALID_CTRF) - } + let report: CTRFReport; + try { + report = parse(fileContent); + } catch (parseError) { + console.error( + `Error: Invalid CTRF report - ${(parseError as Error).message}`, + ); + process.exit(EXIT_INVALID_CTRF); + } - const testsWithIds: Test[] = report.results.tests.map(test => ({ - ...test, - id: generateTestId(test), - })) + const testsWithIds: Test[] = report.results.tests.map((test) => ({ + ...test, + id: generateTestId(test), + })); - const updatedReport: CTRFReport = { - ...report, - results: { - ...report.results, - tests: testsWithIds, - }, - } + const updatedReport: CTRFReport = { + ...report, + results: { + ...report.results, + tests: testsWithIds, + }, + }; - const output = stringify(updatedReport) + const output = stringify(updatedReport); - if (options.output) { - const outputPath = path.resolve(options.output) - const outputDir = path.dirname(outputPath) + if (options.output) { + const outputPath = path.resolve(options.output); + const outputDir = path.dirname(outputPath); - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }) - } + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } - fs.writeFileSync(outputPath, output, 'utf-8') - console.error(`✓ Generated IDs for ${testsWithIds.length} tests`) - console.error(`✓ Saved to ${options.output}`) - process.exit(EXIT_SUCCESS) - } else { - console.log(output) - process.exit(EXIT_SUCCESS) - } - } catch (error) { - console.error(`Error: ${(error as Error).message}`) - process.exit(EXIT_GENERAL_ERROR) - } + fs.writeFileSync(outputPath, output, "utf-8"); + console.error(`✓ Generated IDs for ${testsWithIds.length} tests`); + console.error(`✓ Saved to ${options.output}`); + process.exit(EXIT_SUCCESS); + } else { + console.log(output); + process.exit(EXIT_SUCCESS); + } + } catch (error) { + console.error(`Error: ${(error as Error).message}`); + process.exit(EXIT_GENERAL_ERROR); + } } diff --git a/src/merge.test.ts b/src/merge.test.ts index 7ed52b0..cf17fe1 100644 --- a/src/merge.test.ts +++ b/src/merge.test.ts @@ -1,234 +1,234 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest' -import fs from 'fs' -import path from 'path' -import os from 'os' -import { mergeReports } from './merge.js' - -describe('mergeReports', () => { - let tmpDir: string - let testReportDir: string - let testReport1: string - let testReport2: string - - beforeEach(() => { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ctrf-merge-test-')) - testReportDir = path.join(tmpDir, 'reports') - fs.mkdirSync(testReportDir, { recursive: true }) - - const report1 = { - results: { - tool: { name: 'test-tool' }, - summary: { - tests: 2, - passed: 1, - failed: 1, - skipped: 0, - pending: 0, - other: 0, - start: 1708979371669, - stop: 1708979388927, - }, - tests: [ - { - name: 'test 1', - status: 'passed', - duration: 100, - }, - { - name: 'test 2', - status: 'failed', - duration: 200, - }, - ], - }, - } - - const report2 = { - results: { - tool: { name: 'test-tool' }, - summary: { - tests: 1, - passed: 1, - failed: 0, - skipped: 0, - pending: 0, - other: 0, - start: 1708979400000, - stop: 1708979410000, - }, - tests: [ - { - name: 'test 3', - status: 'passed', - duration: 150, - }, - ], - }, - } - - testReport1 = path.join(testReportDir, 'report1.json') - testReport2 = path.join(testReportDir, 'report2.json') - - fs.writeFileSync(testReport1, JSON.stringify(report1, null, 2)) - fs.writeFileSync(testReport2, JSON.stringify(report2, null, 2)) - }) - - afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }) - }) - - describe('output path handling', () => { - it('should save with custom filename in input directory', async () => { - const outputFilename = 'my-merged-report.json' - await mergeReports(testReportDir, outputFilename, true) - - const outputPath = path.join(testReportDir, outputFilename) - expect(fs.existsSync(outputPath)).toBe(true) - - const merged = JSON.parse(fs.readFileSync(outputPath, 'utf8')) - expect(merged.results.summary.tests).toBe(3) - expect(merged.results.summary.passed).toBe(2) - expect(merged.results.summary.failed).toBe(1) - }) - - it('should save with relative path from current directory', async () => { - const outputDir = path.join(tmpDir, 'output') - const outputPath = path.join(outputDir, 'merged.json') - const relativeOutputPath = path.join(outputDir, 'merged.json') - - const originalCwd = process.cwd() - try { - process.chdir(tmpDir) - await mergeReports(testReportDir, relativeOutputPath, true) - - expect(fs.existsSync(outputPath)).toBe(true) - - const merged = JSON.parse(fs.readFileSync(outputPath, 'utf8')) - expect(merged.results.summary.tests).toBe(3) - expect(merged.results.summary.passed).toBe(2) - expect(merged.results.summary.failed).toBe(1) - } finally { - process.chdir(originalCwd) - } - }) - - it('should save to directory with default filename', async () => { - const outputDirPath = path.join(tmpDir, 'output') - await mergeReports(testReportDir, outputDirPath + '/', true) - - const expectedOutputPath = path.join(outputDirPath, 'ctrf-report.json') - expect(fs.existsSync(expectedOutputPath)).toBe(true) - - const merged = JSON.parse(fs.readFileSync(expectedOutputPath, 'utf8')) - expect(merged.results.summary.tests).toBe(3) - expect(merged.results.summary.passed).toBe(2) - expect(merged.results.summary.failed).toBe(1) - }) - - it('should save to absolute path', async () => { - const outputAbsPath = path.join(tmpDir, 'absolute-output', 'merged.json') - await mergeReports(testReportDir, outputAbsPath, true) - - expect(fs.existsSync(outputAbsPath)).toBe(true) - - const merged = JSON.parse(fs.readFileSync(outputAbsPath, 'utf8')) - expect(merged.results.summary.tests).toBe(3) - expect(merged.results.summary.passed).toBe(2) - expect(merged.results.summary.failed).toBe(1) - }) - - it('should detect directory without trailing slash and use default filename', async () => { - const outputDirPath = path.join(tmpDir, 'output-no-slash') - fs.mkdirSync(outputDirPath, { recursive: true }) - await mergeReports(testReportDir, outputDirPath, true) - - const expectedOutputPath = path.join(outputDirPath, 'ctrf-report.json') - expect(fs.existsSync(expectedOutputPath)).toBe(true) - - const merged = JSON.parse(fs.readFileSync(expectedOutputPath, 'utf8')) - expect(merged.results.summary.tests).toBe(3) - }) - - it('should create nested output directories if they do not exist', async () => { - const outputPath = path.join( - tmpDir, - 'deep', - 'nested', - 'output', - 'merged.json' - ) - await mergeReports(testReportDir, outputPath, true) - - expect(fs.existsSync(outputPath)).toBe(true) - - const merged = JSON.parse(fs.readFileSync(outputPath, 'utf8')) - expect(merged.results.summary.tests).toBe(3) - }) - }) - - describe('report merging', () => { - it('should correctly merge test summaries', async () => { - const outputPath = path.join(tmpDir, 'merged.json') - await mergeReports(testReportDir, outputPath, true) - - const merged = JSON.parse(fs.readFileSync(outputPath, 'utf8')) - - expect(merged.results.summary.tests).toBe(3) - expect(merged.results.summary.passed).toBe(2) - expect(merged.results.summary.failed).toBe(1) - expect(merged.results.summary.skipped).toBe(0) - expect(merged.results.summary.pending).toBe(0) - expect(merged.results.summary.other).toBe(0) - }) - - it('should combine all test cases from multiple reports', async () => { - const outputPath = path.join(tmpDir, 'merged.json') - await mergeReports(testReportDir, outputPath, true) - - const merged = JSON.parse(fs.readFileSync(outputPath, 'utf8')) - - expect(merged.results.tests.length).toBe(3) - expect(merged.results.tests[0].name).toBe('test 1') - expect(merged.results.tests[1].name).toBe('test 2') - expect(merged.results.tests[2].name).toBe('test 3') - }) - - it('should use min start time and max stop time from all reports', async () => { - const outputPath = path.join(tmpDir, 'merged.json') - await mergeReports(testReportDir, outputPath, true) - - const merged = JSON.parse(fs.readFileSync(outputPath, 'utf8')) - - expect(merged.results.summary.start).toBe(1708979371669) - expect(merged.results.summary.stop).toBe(1708979410000) - }) - }) - - describe('file retention', () => { - it('should keep original reports when keepReports is true', async () => { - const outputPath = path.join(tmpDir, 'merged.json') - await mergeReports(testReportDir, outputPath, true) - - expect(fs.existsSync(testReport1)).toBe(true) - expect(fs.existsSync(testReport2)).toBe(true) - }) - - it('should delete original reports when keepReports is false', async () => { - const outputPath = path.join(tmpDir, 'merged.json') - await mergeReports(testReportDir, outputPath, false) - - expect(fs.existsSync(testReport1)).toBe(false) - expect(fs.existsSync(testReport2)).toBe(false) - expect(fs.existsSync(outputPath)).toBe(true) - }) - - it('should not delete the output report itself', async () => { - const outputFilename = 'ctrf-report.json' - const outputPath = path.join(testReportDir, outputFilename) - - await mergeReports(testReportDir, outputFilename, false) - - expect(fs.existsSync(outputPath)).toBe(true) - }) - }) -}) +import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { mergeReports } from "./merge.js"; + +describe("mergeReports", () => { + let tmpDir: string; + let testReportDir: string; + let testReport1: string; + let testReport2: string; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ctrf-merge-test-")); + testReportDir = path.join(tmpDir, "reports"); + fs.mkdirSync(testReportDir, { recursive: true }); + + const report1 = { + results: { + tool: { name: "test-tool" }, + summary: { + tests: 2, + passed: 1, + failed: 1, + skipped: 0, + pending: 0, + other: 0, + start: 1708979371669, + stop: 1708979388927, + }, + tests: [ + { + name: "test 1", + status: "passed", + duration: 100, + }, + { + name: "test 2", + status: "failed", + duration: 200, + }, + ], + }, + }; + + const report2 = { + results: { + tool: { name: "test-tool" }, + summary: { + tests: 1, + passed: 1, + failed: 0, + skipped: 0, + pending: 0, + other: 0, + start: 1708979400000, + stop: 1708979410000, + }, + tests: [ + { + name: "test 3", + status: "passed", + duration: 150, + }, + ], + }, + }; + + testReport1 = path.join(testReportDir, "report1.json"); + testReport2 = path.join(testReportDir, "report2.json"); + + fs.writeFileSync(testReport1, JSON.stringify(report1, null, 2)); + fs.writeFileSync(testReport2, JSON.stringify(report2, null, 2)); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + describe("output path handling", () => { + it("should save with custom filename in input directory", async () => { + const outputFilename = "my-merged-report.json"; + await mergeReports(testReportDir, outputFilename, true); + + const outputPath = path.join(testReportDir, outputFilename); + expect(fs.existsSync(outputPath)).toBe(true); + + const merged = JSON.parse(fs.readFileSync(outputPath, "utf8")); + expect(merged.results.summary.tests).toBe(3); + expect(merged.results.summary.passed).toBe(2); + expect(merged.results.summary.failed).toBe(1); + }); + + it("should save with relative path from current directory", async () => { + const outputDir = path.join(tmpDir, "output"); + const outputPath = path.join(outputDir, "merged.json"); + const relativeOutputPath = path.join(outputDir, "merged.json"); + + const originalCwd = process.cwd(); + try { + process.chdir(tmpDir); + await mergeReports(testReportDir, relativeOutputPath, true); + + expect(fs.existsSync(outputPath)).toBe(true); + + const merged = JSON.parse(fs.readFileSync(outputPath, "utf8")); + expect(merged.results.summary.tests).toBe(3); + expect(merged.results.summary.passed).toBe(2); + expect(merged.results.summary.failed).toBe(1); + } finally { + process.chdir(originalCwd); + } + }); + + it("should save to directory with default filename", async () => { + const outputDirPath = path.join(tmpDir, "output"); + await mergeReports(testReportDir, `${outputDirPath}/`, true); + + const expectedOutputPath = path.join(outputDirPath, "ctrf-report.json"); + expect(fs.existsSync(expectedOutputPath)).toBe(true); + + const merged = JSON.parse(fs.readFileSync(expectedOutputPath, "utf8")); + expect(merged.results.summary.tests).toBe(3); + expect(merged.results.summary.passed).toBe(2); + expect(merged.results.summary.failed).toBe(1); + }); + + it("should save to absolute path", async () => { + const outputAbsPath = path.join(tmpDir, "absolute-output", "merged.json"); + await mergeReports(testReportDir, outputAbsPath, true); + + expect(fs.existsSync(outputAbsPath)).toBe(true); + + const merged = JSON.parse(fs.readFileSync(outputAbsPath, "utf8")); + expect(merged.results.summary.tests).toBe(3); + expect(merged.results.summary.passed).toBe(2); + expect(merged.results.summary.failed).toBe(1); + }); + + it("should detect directory without trailing slash and use default filename", async () => { + const outputDirPath = path.join(tmpDir, "output-no-slash"); + fs.mkdirSync(outputDirPath, { recursive: true }); + await mergeReports(testReportDir, outputDirPath, true); + + const expectedOutputPath = path.join(outputDirPath, "ctrf-report.json"); + expect(fs.existsSync(expectedOutputPath)).toBe(true); + + const merged = JSON.parse(fs.readFileSync(expectedOutputPath, "utf8")); + expect(merged.results.summary.tests).toBe(3); + }); + + it("should create nested output directories if they do not exist", async () => { + const outputPath = path.join( + tmpDir, + "deep", + "nested", + "output", + "merged.json", + ); + await mergeReports(testReportDir, outputPath, true); + + expect(fs.existsSync(outputPath)).toBe(true); + + const merged = JSON.parse(fs.readFileSync(outputPath, "utf8")); + expect(merged.results.summary.tests).toBe(3); + }); + }); + + describe("report merging", () => { + it("should correctly merge test summaries", async () => { + const outputPath = path.join(tmpDir, "merged.json"); + await mergeReports(testReportDir, outputPath, true); + + const merged = JSON.parse(fs.readFileSync(outputPath, "utf8")); + + expect(merged.results.summary.tests).toBe(3); + expect(merged.results.summary.passed).toBe(2); + expect(merged.results.summary.failed).toBe(1); + expect(merged.results.summary.skipped).toBe(0); + expect(merged.results.summary.pending).toBe(0); + expect(merged.results.summary.other).toBe(0); + }); + + it("should combine all test cases from multiple reports", async () => { + const outputPath = path.join(tmpDir, "merged.json"); + await mergeReports(testReportDir, outputPath, true); + + const merged = JSON.parse(fs.readFileSync(outputPath, "utf8")); + + expect(merged.results.tests.length).toBe(3); + expect(merged.results.tests[0].name).toBe("test 1"); + expect(merged.results.tests[1].name).toBe("test 2"); + expect(merged.results.tests[2].name).toBe("test 3"); + }); + + it("should use min start time and max stop time from all reports", async () => { + const outputPath = path.join(tmpDir, "merged.json"); + await mergeReports(testReportDir, outputPath, true); + + const merged = JSON.parse(fs.readFileSync(outputPath, "utf8")); + + expect(merged.results.summary.start).toBe(1708979371669); + expect(merged.results.summary.stop).toBe(1708979410000); + }); + }); + + describe("file retention", () => { + it("should keep original reports when keepReports is true", async () => { + const outputPath = path.join(tmpDir, "merged.json"); + await mergeReports(testReportDir, outputPath, true); + + expect(fs.existsSync(testReport1)).toBe(true); + expect(fs.existsSync(testReport2)).toBe(true); + }); + + it("should delete original reports when keepReports is false", async () => { + const outputPath = path.join(tmpDir, "merged.json"); + await mergeReports(testReportDir, outputPath, false); + + expect(fs.existsSync(testReport1)).toBe(false); + expect(fs.existsSync(testReport2)).toBe(false); + expect(fs.existsSync(outputPath)).toBe(true); + }); + + it("should not delete the output report itself", async () => { + const outputFilename = "ctrf-report.json"; + const outputPath = path.join(testReportDir, outputFilename); + + await mergeReports(testReportDir, outputFilename, false); + + expect(fs.existsSync(outputPath)).toBe(true); + }); + }); +}); diff --git a/src/merge.ts b/src/merge.ts index 2a99af8..d79f5ab 100644 --- a/src/merge.ts +++ b/src/merge.ts @@ -1,131 +1,131 @@ -import fs from 'fs' -import path from 'path' +import fs from "node:fs"; +import path from "node:path"; export async function mergeReports( - directory: string, - output: string, - keepReports: boolean, - outputDir?: string + directory: string, + output: string, + keepReports: boolean, + outputDir?: string, ) { - try { - const directoryPath = path.resolve(directory) - - let outputPath: string - - if (outputDir) { - console.warn( - 'Warning: --output-dir is deprecated. Use --output with a path instead.' - ) - const outputFileName = output - const resolvedOutputDir = path.resolve(outputDir) - outputPath = path.join(resolvedOutputDir, outputFileName) - } else if (output.includes('/') || output.includes('\\')) { - const resolvedPath = path.resolve(output) - - const endsWithSeparator = output.endsWith('/') || output.endsWith('\\') - const isExistingDirectory = - fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory() - const hasNoExtension = path.extname(output) === '' - - if (endsWithSeparator || isExistingDirectory || hasNoExtension) { - outputPath = path.join(resolvedPath, 'ctrf-report.json') - } else { - outputPath = resolvedPath - } - } else { - outputPath = path.join(directoryPath, output) - } - - console.log('Merging CTRF reports...') - - const files = fs.readdirSync(directoryPath) - - files.forEach(file => { - console.log('Found file:', file) - }) - - const ctrfReportFiles = files.filter(file => { - try { - if (path.extname(file) !== '.json') { - console.log(`Skipping non-CTRF file: ${file}`) - return false - } - const filePath = path.join(directoryPath, file) - const fileContent = fs.readFileSync(filePath, 'utf8') - const jsonData = JSON.parse(fileContent) - if (!Object.prototype.hasOwnProperty.call(jsonData, 'results')) { - console.log(`Skipping non-CTRF file: ${file}`) - return false - } - return true - } catch (error) { - console.error(`Error reading JSON file '${file}':`, error) - return false - } - }) - - if (ctrfReportFiles.length === 0) { - console.log('No CTRF reports found in the specified directory.') - return - } - - const outputDirPath = path.dirname(outputPath) - if (!fs.existsSync(outputDirPath)) { - fs.mkdirSync(outputDirPath, { recursive: true }) - console.log(`Created output directory: ${outputDirPath}`) - } - - const mergedReport = ctrfReportFiles - .map(file => { - console.log('Merging report:', file) - const filePath = path.join(directoryPath, file) - const fileContent = fs.readFileSync(filePath, 'utf8') - return JSON.parse(fileContent) - }) - .reduce( - (acc, curr) => { - if (!acc.results) { - return curr - } - - acc.results.summary.tests += curr.results.summary.tests - acc.results.summary.passed += curr.results.summary.passed - acc.results.summary.failed += curr.results.summary.failed - acc.results.summary.skipped += curr.results.summary.skipped - acc.results.summary.pending += curr.results.summary.pending - acc.results.summary.other += curr.results.summary.other - - acc.results.tests.push(...curr.results.tests) - - acc.results.summary.start = Math.min( - acc.results.summary.start, - curr.results.summary.start - ) - acc.results.summary.stop = Math.max( - acc.results.summary.stop, - curr.results.summary.stop - ) - - return acc - }, - { results: null } - ) - - fs.writeFileSync(outputPath, JSON.stringify(mergedReport, null, 2)) - - if (!keepReports) { - const outputFileName = path.basename(outputPath) - ctrfReportFiles.forEach(file => { - const filePath = path.join(directoryPath, file) - if (file !== outputFileName) { - fs.unlinkSync(filePath) - } - }) - } - - console.log('CTRF reports merged successfully.') - console.log(`Merged report saved to: ${outputPath}`) - } catch (error) { - console.error('Error merging CTRF reports:', error) - } + try { + const directoryPath = path.resolve(directory); + + let outputPath: string; + + if (outputDir) { + console.warn( + "Warning: --output-dir is deprecated. Use --output with a path instead.", + ); + const outputFileName = output; + const resolvedOutputDir = path.resolve(outputDir); + outputPath = path.join(resolvedOutputDir, outputFileName); + } else if (output.includes("/") || output.includes("\\")) { + const resolvedPath = path.resolve(output); + + const endsWithSeparator = output.endsWith("/") || output.endsWith("\\"); + const isExistingDirectory = + fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory(); + const hasNoExtension = path.extname(output) === ""; + + if (endsWithSeparator || isExistingDirectory || hasNoExtension) { + outputPath = path.join(resolvedPath, "ctrf-report.json"); + } else { + outputPath = resolvedPath; + } + } else { + outputPath = path.join(directoryPath, output); + } + + console.log("Merging CTRF reports..."); + + const files = fs.readdirSync(directoryPath); + + files.forEach((file) => { + console.log("Found file:", file); + }); + + const ctrfReportFiles = files.filter((file) => { + try { + if (path.extname(file) !== ".json") { + console.log(`Skipping non-CTRF file: ${file}`); + return false; + } + const filePath = path.join(directoryPath, file); + const fileContent = fs.readFileSync(filePath, "utf8"); + const jsonData = JSON.parse(fileContent); + if (!Object.hasOwn(jsonData, "results")) { + console.log(`Skipping non-CTRF file: ${file}`); + return false; + } + return true; + } catch (error) { + console.error(`Error reading JSON file '${file}':`, error); + return false; + } + }); + + if (ctrfReportFiles.length === 0) { + console.log("No CTRF reports found in the specified directory."); + return; + } + + const outputDirPath = path.dirname(outputPath); + if (!fs.existsSync(outputDirPath)) { + fs.mkdirSync(outputDirPath, { recursive: true }); + console.log(`Created output directory: ${outputDirPath}`); + } + + const mergedReport = ctrfReportFiles + .map((file) => { + console.log("Merging report:", file); + const filePath = path.join(directoryPath, file); + const fileContent = fs.readFileSync(filePath, "utf8"); + return JSON.parse(fileContent); + }) + .reduce( + (acc, curr) => { + if (!acc.results) { + return curr; + } + + acc.results.summary.tests += curr.results.summary.tests; + acc.results.summary.passed += curr.results.summary.passed; + acc.results.summary.failed += curr.results.summary.failed; + acc.results.summary.skipped += curr.results.summary.skipped; + acc.results.summary.pending += curr.results.summary.pending; + acc.results.summary.other += curr.results.summary.other; + + acc.results.tests.push(...curr.results.tests); + + acc.results.summary.start = Math.min( + acc.results.summary.start, + curr.results.summary.start, + ); + acc.results.summary.stop = Math.max( + acc.results.summary.stop, + curr.results.summary.stop, + ); + + return acc; + }, + { results: null }, + ); + + fs.writeFileSync(outputPath, JSON.stringify(mergedReport, null, 2)); + + if (!keepReports) { + const outputFileName = path.basename(outputPath); + ctrfReportFiles.forEach((file) => { + const filePath = path.join(directoryPath, file); + if (file !== outputFileName) { + fs.unlinkSync(filePath); + } + }); + } + + console.log("CTRF reports merged successfully."); + console.log(`Merged report saved to: ${outputPath}`); + } catch (error) { + console.error("Error merging CTRF reports:", error); + } } diff --git a/src/validate.test.ts b/src/validate.test.ts index 85bba64..3387099 100644 --- a/src/validate.test.ts +++ b/src/validate.test.ts @@ -1,195 +1,188 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import fs from 'fs' -import path from 'path' -import os from 'os' -import { - ReportBuilder, - TestBuilder, - validate, - isCTRFReport, - stringify, - parse, -} from 'ctrf' -import { validateReport } from './validate.js' - -describe('validateReport', () => { - let tmpDir: string - let validReportPath: string - let invalidReportPath: string - let exitSpy: ReturnType - let consoleLogSpy: ReturnType - let consoleErrorSpy: ReturnType - - const validReport = new ReportBuilder() - .tool({ name: 'test-tool' }) - .addTest( - new TestBuilder().name('test 1').status('passed').duration(100).build() - ) - .addTest( - new TestBuilder().name('test 2').status('failed').duration(200).build() - ) - .build() - - const invalidReport = { - results: { - tests: [], - }, - } - - beforeEach(() => { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ctrf-validate-test-')) - - validReportPath = path.join(tmpDir, 'valid-report.json') - invalidReportPath = path.join(tmpDir, 'invalid-report.json') - - fs.writeFileSync(validReportPath, JSON.stringify(validReport, null, 2)) - fs.writeFileSync(invalidReportPath, JSON.stringify(invalidReport, null, 2)) - - exitSpy = vi - .spyOn(process, 'exit') - .mockImplementation((() => undefined as never) as any) as any - consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) - consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) - }) - - afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }) - exitSpy.mockRestore() - consoleLogSpy.mockRestore() - consoleErrorSpy.mockRestore() - }) - - describe('validate (non-strict)', () => { - it('should validate a valid CTRF report', async () => { - await validateReport(validReportPath, false) - expect(exitSpy).toHaveBeenCalledWith(0) - expect(consoleLogSpy).toHaveBeenCalledWith( - expect.stringContaining('is valid CTRF') - ) - - const reportContent = fs.readFileSync(validReportPath, 'utf-8') - const parsedReport = parse(reportContent) - const result = validate(parsedReport) - expect(result.valid).toBe(true) - }) - - it('should reject an invalid CTRF report', async () => { - await validateReport(invalidReportPath, false) - expect(exitSpy).toHaveBeenCalledWith(2) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('failed validation') - ) - }) - - it('should exit with code 3 for file not found', async () => { - const nonExistentPath = path.join(tmpDir, 'nonexistent.json') - await validateReport(nonExistentPath, false) - expect(exitSpy).toHaveBeenCalledWith(3) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('File not found') - ) - }) - - it('should exit with code 4 for invalid JSON', async () => { - const invalidJsonPath = path.join(tmpDir, 'invalid.json') - fs.writeFileSync(invalidJsonPath, 'not valid json') - await validateReport(invalidJsonPath, false) - expect(exitSpy).toHaveBeenCalledWith(4) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('Invalid CTRF report') - ) - }) - }) - - describe('validate-strict', () => { - it('should validate a valid CTRF report in strict mode', async () => { - await validateReport(validReportPath, true) - expect(exitSpy).toHaveBeenCalledWith(0) - expect(consoleLogSpy).toHaveBeenCalledWith( - expect.stringContaining('is valid CTRF (strict)') - ) - }) - - it('should reject an invalid CTRF report in strict mode', async () => { - await validateReport(invalidReportPath, true) - expect(exitSpy).toHaveBeenCalledWith(2) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('failed strict validation') - ) - }) - }) - - describe('file not found', () => { - it('should exit with code 3 when file not found', async () => { - const nonExistentPath = path.join(tmpDir, 'nonexistent.json') - await validateReport(nonExistentPath) - expect(exitSpy).toHaveBeenCalledWith(3) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('File not found') - ) - }) - }) - - describe('invalid JSON', () => { - it('should exit with code 4 for invalid CTRF JSON', async () => { - const invalidJsonPath = path.join(tmpDir, 'invalid.json') - fs.writeFileSync(invalidJsonPath, 'not valid json {') - - await validateReport(invalidJsonPath) - expect(exitSpy).toHaveBeenCalledWith(4) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('Invalid CTRF report') - ) - }) - }) - - describe('strict mode error details', () => { - it('should display validation error paths in strict mode', async () => { - const reportWithPath = { - reportFormat: 'CTRF', - specVersion: '1.0.0', - results: { - tool: { name: 'test' }, - summary: {}, - tests: [], - }, - } - - fs.writeFileSync( - invalidReportPath, - JSON.stringify(reportWithPath, null, 2) - ) - - await validateReport(invalidReportPath, true) - expect(exitSpy).toHaveBeenCalledWith(2) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('failed strict validation') - ) - }) - }) - - describe('standard mode error handling', () => { - it('should display validation errors without error.errors array', async () => { - const malformedReport = { - reportFormat: 'WRONG', - specVersion: '1.0.0', - results: { - tool: { name: 'test' }, - summary: {}, - tests: [], - }, - } - - fs.writeFileSync( - invalidReportPath, - JSON.stringify(malformedReport, null, 2) - ) - - await validateReport(invalidReportPath, false) - expect(exitSpy).toHaveBeenCalledWith(2) - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('failed validation') - ) - }) - }) -}) +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { ReportBuilder, TestBuilder, validate, parse } from "ctrf"; +import { validateReport } from "./validate.js"; + +describe("validateReport", () => { + let tmpDir: string; + let validReportPath: string; + let invalidReportPath: string; + let exitSpy: ReturnType; + let consoleLogSpy: ReturnType; + let consoleErrorSpy: ReturnType; + + const validReport = new ReportBuilder() + .tool({ name: "test-tool" }) + .addTest( + new TestBuilder().name("test 1").status("passed").duration(100).build(), + ) + .addTest( + new TestBuilder().name("test 2").status("failed").duration(200).build(), + ) + .build(); + + const invalidReport = { + results: { + tests: [], + }, + }; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ctrf-validate-test-")); + + validReportPath = path.join(tmpDir, "valid-report.json"); + invalidReportPath = path.join(tmpDir, "invalid-report.json"); + + fs.writeFileSync(validReportPath, JSON.stringify(validReport, null, 2)); + fs.writeFileSync(invalidReportPath, JSON.stringify(invalidReport, null, 2)); + + exitSpy = vi + .spyOn(process, "exit") + .mockImplementation((() => undefined as never) as any) as any; + consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + exitSpy.mockRestore(); + consoleLogSpy.mockRestore(); + consoleErrorSpy.mockRestore(); + }); + + describe("validate (non-strict)", () => { + it("should validate a valid CTRF report", async () => { + await validateReport(validReportPath, false); + expect(exitSpy).toHaveBeenCalledWith(0); + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringContaining("is valid CTRF"), + ); + + const reportContent = fs.readFileSync(validReportPath, "utf-8"); + const parsedReport = parse(reportContent); + const result = validate(parsedReport); + expect(result.valid).toBe(true); + }); + + it("should reject an invalid CTRF report", async () => { + await validateReport(invalidReportPath, false); + expect(exitSpy).toHaveBeenCalledWith(2); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("failed validation"), + ); + }); + + it("should exit with code 3 for file not found", async () => { + const nonExistentPath = path.join(tmpDir, "nonexistent.json"); + await validateReport(nonExistentPath, false); + expect(exitSpy).toHaveBeenCalledWith(3); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("File not found"), + ); + }); + + it("should exit with code 4 for invalid JSON", async () => { + const invalidJsonPath = path.join(tmpDir, "invalid.json"); + fs.writeFileSync(invalidJsonPath, "not valid json"); + await validateReport(invalidJsonPath, false); + expect(exitSpy).toHaveBeenCalledWith(4); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Invalid CTRF report"), + ); + }); + }); + + describe("validate-strict", () => { + it("should validate a valid CTRF report in strict mode", async () => { + await validateReport(validReportPath, true); + expect(exitSpy).toHaveBeenCalledWith(0); + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringContaining("is valid CTRF (strict)"), + ); + }); + + it("should reject an invalid CTRF report in strict mode", async () => { + await validateReport(invalidReportPath, true); + expect(exitSpy).toHaveBeenCalledWith(2); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("failed strict validation"), + ); + }); + }); + + describe("file not found", () => { + it("should exit with code 3 when file not found", async () => { + const nonExistentPath = path.join(tmpDir, "nonexistent.json"); + await validateReport(nonExistentPath); + expect(exitSpy).toHaveBeenCalledWith(3); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("File not found"), + ); + }); + }); + + describe("invalid JSON", () => { + it("should exit with code 4 for invalid CTRF JSON", async () => { + const invalidJsonPath = path.join(tmpDir, "invalid.json"); + fs.writeFileSync(invalidJsonPath, "not valid json {"); + + await validateReport(invalidJsonPath); + expect(exitSpy).toHaveBeenCalledWith(4); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("Invalid CTRF report"), + ); + }); + }); + + describe("strict mode error details", () => { + it("should display validation error paths in strict mode", async () => { + const reportWithPath = { + reportFormat: "CTRF", + specVersion: "1.0.0", + results: { + tool: { name: "test" }, + summary: {}, + tests: [], + }, + }; + + fs.writeFileSync( + invalidReportPath, + JSON.stringify(reportWithPath, null, 2), + ); + + await validateReport(invalidReportPath, true); + expect(exitSpy).toHaveBeenCalledWith(2); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("failed strict validation"), + ); + }); + }); + + describe("standard mode error handling", () => { + it("should display validation errors without error.errors array", async () => { + const malformedReport = { + reportFormat: "WRONG", + specVersion: "1.0.0", + results: { + tool: { name: "test" }, + summary: {}, + tests: [], + }, + }; + + fs.writeFileSync( + invalidReportPath, + JSON.stringify(malformedReport, null, 2), + ); + + await validateReport(invalidReportPath, false); + expect(exitSpy).toHaveBeenCalledWith(2); + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("failed validation"), + ); + }); + }); +}); diff --git a/src/validate.ts b/src/validate.ts index 3e2c305..22751d1 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -1,82 +1,82 @@ -import fs from 'fs' -import path from 'path' +import fs from "node:fs"; +import path from "node:path"; import { - parse, - validate, - validateStrict, - ValidationResult, - ValidationError, -} from 'ctrf' + parse, + validate, + validateStrict, + type ValidationResult, + ValidationError, +} from "ctrf"; import { - EXIT_SUCCESS, - EXIT_GENERAL_ERROR, - EXIT_VALIDATION_FAILED, - EXIT_FILE_NOT_FOUND, - EXIT_INVALID_CTRF, -} from './exit-codes.js' + EXIT_SUCCESS, + EXIT_GENERAL_ERROR, + EXIT_VALIDATION_FAILED, + EXIT_FILE_NOT_FOUND, + EXIT_INVALID_CTRF, +} from "./exit-codes.js"; export async function validateReport( - filePath: string, - strict: boolean = false + filePath: string, + strict: boolean = false, ): Promise { - try { - const resolvedPath = path.resolve(filePath) + try { + const resolvedPath = path.resolve(filePath); - if (!fs.existsSync(resolvedPath)) { - console.error(`Error: File not found: ${resolvedPath}`) - process.exit(EXIT_FILE_NOT_FOUND) - } + if (!fs.existsSync(resolvedPath)) { + console.error(`Error: File not found: ${resolvedPath}`); + process.exit(EXIT_FILE_NOT_FOUND); + } - const fileContent = fs.readFileSync(resolvedPath, 'utf-8') + const fileContent = fs.readFileSync(resolvedPath, "utf-8"); - let report - try { - report = parse(fileContent) - } catch (parseError) { - console.error( - `Error: Invalid CTRF report - ${(parseError as Error).message}` - ) - process.exit(EXIT_INVALID_CTRF) - } + let report: ReturnType; + try { + report = parse(fileContent); + } catch (parseError) { + console.error( + `Error: Invalid CTRF report - ${(parseError as Error).message}`, + ); + process.exit(EXIT_INVALID_CTRF); + } - if (strict) { - try { - validateStrict(report) - console.log(`✓ ${path.basename(filePath)} is valid CTRF (strict)`) - process.exit(EXIT_SUCCESS) - } catch (error) { - console.error(`✗ ${path.basename(filePath)} failed strict validation:`) - if (error instanceof ValidationError && error.errors) { - error.errors.forEach(err => { - const errPath = err.path || '' - const errMessage = err.message || String(err) - console.error(` - ${errPath ? errPath + ': ' : ''}${errMessage}`) - }) - } else { - console.error(` - ${(error as Error).message}`) - } - process.exit(EXIT_VALIDATION_FAILED) - } - } else { - const validationResult: ValidationResult = validate(report) + if (strict) { + try { + validateStrict(report); + console.log(`✓ ${path.basename(filePath)} is valid CTRF (strict)`); + process.exit(EXIT_SUCCESS); + } catch (error) { + console.error(`✗ ${path.basename(filePath)} failed strict validation:`); + if (error instanceof ValidationError && error.errors) { + error.errors.forEach((err) => { + const errPath = err.path || ""; + const errMessage = err.message || String(err); + console.error(` - ${errPath ? `${errPath}: ` : ""}${errMessage}`); + }); + } else { + console.error(` - ${(error as Error).message}`); + } + process.exit(EXIT_VALIDATION_FAILED); + } + } else { + const validationResult: ValidationResult = validate(report); - if (validationResult.valid) { - console.log(`✓ ${path.basename(filePath)} is valid CTRF`) - process.exit(EXIT_SUCCESS) - } else { - console.error(`✗ ${path.basename(filePath)} failed validation:`) - if (validationResult.errors && validationResult.errors.length > 0) { - validationResult.errors.forEach(error => { - const errPath = error.path || '' - const errMessage = error.message || String(error) - console.error(` - ${errPath ? errPath + ': ' : ''}${errMessage}`) - }) - } - process.exit(EXIT_VALIDATION_FAILED) - } - } - } catch (error) { - console.error(`Error: ${(error as Error).message}`) - process.exit(EXIT_GENERAL_ERROR) - } + if (validationResult.valid) { + console.log(`✓ ${path.basename(filePath)} is valid CTRF`); + process.exit(EXIT_SUCCESS); + } else { + console.error(`✗ ${path.basename(filePath)} failed validation:`); + if (validationResult.errors && validationResult.errors.length > 0) { + validationResult.errors.forEach((error) => { + const errPath = error.path || ""; + const errMessage = error.message || String(error); + console.error(` - ${errPath ? `${errPath}: ` : ""}${errMessage}`); + }); + } + process.exit(EXIT_VALIDATION_FAILED); + } + } + } catch (error) { + console.error(`Error: ${(error as Error).message}`); + process.exit(EXIT_GENERAL_ERROR); + } } diff --git a/tsconfig.json b/tsconfig.json index 005ab5c..ccad240 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,24 @@ { - "compilerOptions": { - "target": "ES2023", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "outDir": "./dist", - "rootDir": "./src", - "declaration": true, - "declarationDir": "./dist", - "forceConsistentCasingInFileNames": true - }, - "include": ["src/**/*"], - "exclude": [ - "src/__tests__/**/*", - "dist/**/*", - "node_modules/**/*", - "src/test-utils/**/*", - "scripts/**/*" - ] + "compilerOptions": { + "target": "ES2023", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationDir": "./dist", + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": [ + "src/__tests__/**/*", + "dist/**/*", + "node_modules/**/*", + "src/test-utils/**/*", + "scripts/**/*" + ] } diff --git a/tsconfig.test.json b/tsconfig.test.json index d318266..4648a43 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,11 +1,11 @@ { - "extends": "./tsconfig.json", - "compilerOptions": { - "types": ["vitest/globals", "node"], - "moduleResolution": "bundler", - "allowImportingTsExtensions": false, - "noEmit": true - }, - "include": ["src/**/*"], - "exclude": ["dist/**/*", "node_modules/**/*"] + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["vitest/globals", "node"], + "moduleResolution": "bundler", + "allowImportingTsExtensions": false, + "noEmit": true + }, + "include": ["src/**/*"], + "exclude": ["dist/**/*", "node_modules/**/*"] } diff --git a/vitest.config.mts b/vitest.config.mts index 4ccca2f..96a32b8 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -1,25 +1,25 @@ -import { defineConfig } from 'vitest/config' +import { defineConfig } from "vitest/config"; export default defineConfig({ - test: { - reporters: ['default', '@d2t/vitest-ctrf-json-reporter'], - coverage: { - provider: 'v8', - reporter: ['text', 'json', 'html'], - exclude: [ - 'node_modules/', - 'dist/', - 'coverage/', - '**/*.d.ts', - '**/*.test.ts', - '**/*.spec.ts', - 'src/test-utils/**', - 'ctrf/', - ], - }, - environment: 'node', - globals: false, - include: ['src/**/*.{test,spec}.{js,ts}'], - exclude: ['node_modules/', 'dist/', 'coverage/', 'src/test-utils/**'], - }, -}) + test: { + reporters: ["default", "@d2t/vitest-ctrf-json-reporter"], + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], + exclude: [ + "node_modules/", + "dist/", + "coverage/", + "**/*.d.ts", + "**/*.test.ts", + "**/*.spec.ts", + "src/test-utils/**", + "ctrf/", + ], + }, + environment: "node", + globals: false, + include: ["src/**/*.{test,spec}.{js,ts}"], + exclude: ["node_modules/", "dist/", "coverage/", "src/test-utils/**"], + }, +});