modified: .gitignore

modified:   .vscode/launch.json
	new file:   e2e/playwright-collect-coverage.spec.ts
	modified:   package-lock.json
	modified:   package.json
	modified:   playwright.config.ts
	new file:   scripts/merge-lcov.cjs
	new file:   scripts/normalize-lcov.cjs
	new file:   scripts/playwright-collect-coverage.cjs
	new file:   scripts/remap-and-report.cjs
	modified:   vite.config.ts
	new file:   vitest.config.ts
This commit is contained in:
2025-12-02 16:14:12 +10:00
parent d0eb4e953c
commit 6192bd20eb
12 changed files with 1134 additions and 10 deletions
+1 -1
View File
@@ -9,4 +9,4 @@ playwright-report/
coverage/ coverage/
build/ build/
testrun.log testrun.log
e2e-coverage-report/ coverage-reports/
+10 -3
View File
@@ -21,9 +21,9 @@
"request": "launch", "request": "launch",
"command": "npm run test:e2e", "command": "npm run test:e2e",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"env": { // "env": {
"CHROME_PATH": "<path to flatpak chrome wrapper>" // "CHROME_PATH": "<path to flatpak chrome wrapper>"
} // }
}, },
{ {
"name": "Unit Test Coverage", "name": "Unit Test Coverage",
@@ -38,6 +38,13 @@
"request": "launch", "request": "launch",
"command": "npx tsc -noEmit && npm run build", "command": "npx tsc -noEmit && npm run build",
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}"
},
{
"name": "Full CI Coverage test",
"type": "node-terminal",
"request": "launch",
"command": "npm run coverage:ci",
"cwd": "${workspaceFolder}"
} }
] ]
} }
+57
View File
@@ -0,0 +1,57 @@
// e2e/playwright-collect-coverage.spec.ts
import fs from 'fs';
import path from 'path';
import { test } from '@playwright/test';
const EVAL_TIMEOUT_MS = 5000;
const WRITE_TIMEOUT_MS = 15000;
function withTimeout<T>(p: Promise<T>, ms: number, onTimeout?: () => void) {
let timer: NodeJS.Timeout | undefined;
const timeout = new Promise<T>((res) => {
timer = setTimeout(() => {
onTimeout?.();
// @ts-ignore resolve null on timeout
res(null);
}, ms);
});
return Promise.race([p, timeout]).then((r) => {
if (timer) clearTimeout(timer);
return r;
});
}
test.afterEach(async ({ page }, testInfo) => {
try {
console.log(`Collecting coverage for: ${testInfo.title}`);
// Evaluate coverage in page, but don't hang forever
const evalPromise = page.evaluate(() => (globalThis as any).__coverage__ || null);
const coverage = await withTimeout(evalPromise, EVAL_TIMEOUT_MS, () => {
console.warn('page.evaluate for coverage timed out');
});
if (!coverage || Object.keys(coverage).length === 0) {
console.log('No coverage collected (empty or timed out).');
return;
}
const dir = path.join(process.cwd(), '.nyc_output');
await fs.promises.mkdir(dir, { recursive: true });
const safeTitle = (testInfo.title || 'test').replace(/[^a-zA-Z0-9-_]/g, '_').slice(0, 120);
const worker = typeof testInfo.workerIndex === 'number' ? testInfo.workerIndex : process.pid;
const filename = path.join(dir, `coverage-e2e-w${worker}-${Date.now()}-${safeTitle}.json`);
// write asynchronously, bounded by timeout
const writePromise = fs.promises.writeFile(filename, JSON.stringify(coverage));
await withTimeout(writePromise, WRITE_TIMEOUT_MS, () => {
console.warn('Writing coverage file timed out');
});
console.log('Wrote coverage:', filename);
} catch (error: unknown) {
const msg = error instanceof Error ? error.message : String(error);
console.warn('Failed to collect coverage from page:', msg);
}
});
+757
View File
@@ -39,15 +39,22 @@
"@vitejs/plugin-react": "^5.1.1", "@vitejs/plugin-react": "^5.1.1",
"@vitest/coverage-v8": "^4.0.14", "@vitest/coverage-v8": "^4.0.14",
"@vitest/ui": "^4.0.14", "@vitest/ui": "^4.0.14",
"cross-env": "^10.1.0",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24", "eslint-plugin-react-refresh": "^0.4.24",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-lib-source-maps": "^5.0.6",
"istanbul-reports": "^3.2.0",
"jsdom": "^27.2.0", "jsdom": "^27.2.0",
"lcov-result-merger": "^5.0.1",
"msw": "^2.12.3", "msw": "^2.12.3",
"rollup-plugin-visualizer": "^6.0.5", "rollup-plugin-visualizer": "^6.0.5",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.48.0", "typescript-eslint": "^8.48.0",
"vite": "^7.2.6", "vite": "^7.2.6",
"vite-plugin-istanbul": "^7.2.1",
"vitest": "^4.0.14" "vitest": "^4.0.14"
} }
}, },
@@ -754,6 +761,13 @@
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@epic-web/invariant": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
"dev": true,
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12", "version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
@@ -1525,6 +1539,226 @@
} }
} }
}, },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/@isaacs/cliui/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==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@isaacs/cliui/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==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
"integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"camelcase": "^5.3.1",
"find-up": "^4.1.0",
"get-package-type": "^0.1.0",
"js-yaml": "^3.13.1",
"resolve-from": "^5.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"license": "MIT",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -1832,6 +2066,44 @@
} }
} }
}, },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@nodelib/fs.stat": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/@nodelib/fs.walk": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@open-draft/deferred-promise": { "node_modules/@open-draft/deferred-promise": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz",
@@ -1857,6 +2129,17 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@playwright/test": { "node_modules/@playwright/test": {
"version": "1.57.0", "version": "1.57.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz",
@@ -3151,6 +3434,16 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001757", "version": "1.0.30001757",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
@@ -3341,6 +3634,24 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/cross-env": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
"integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@epic-web/invariant": "^1.0.0",
"cross-spawn": "^7.0.6"
},
"bin": {
"cross-env": "dist/bin/cross-env.js",
"cross-env-shell": "dist/bin/cross-env-shell.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -3507,6 +3818,13 @@
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
"license": "MIT"
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.262", "version": "1.5.262",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz",
@@ -3812,6 +4130,20 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/esquery": { "node_modules/esquery": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
@@ -3885,6 +4217,36 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/fast-glob/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fast-json-stable-stringify": { "node_modules/fast-json-stable-stringify": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -3899,6 +4261,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fastq": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
}
},
"node_modules/fdir": { "node_modules/fdir": {
"version": "6.5.0", "version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -4018,6 +4390,23 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/formdata-polyfill": { "node_modules/formdata-polyfill": {
"version": "4.0.10", "version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@@ -4075,6 +4464,37 @@
"node": "6.* || 8.* || >= 10.*" "node": "6.* || 8.* || >= 10.*"
} }
}, },
"node_modules/get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -4428,6 +4848,23 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/istanbul-lib-instrument": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
"integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@babel/core": "^7.23.9",
"@babel/parser": "^7.23.9",
"@istanbuljs/schema": "^0.1.3",
"istanbul-lib-coverage": "^3.2.0",
"semver": "^7.5.4"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-lib-report": { "node_modules/istanbul-lib-report": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
@@ -4472,6 +4909,22 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -4605,6 +5058,82 @@
"json-buffer": "3.0.1" "json-buffer": "3.0.1"
} }
}, },
"node_modules/lcov-result-merger": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/lcov-result-merger/-/lcov-result-merger-5.0.1.tgz",
"integrity": "sha512-i53RjTYfqbHgerqGtuJjDfARDU340zNxXrJudQZU3o8ak9rrx8FDQUKf38Cjm6MtbqonqiDFmoKuUe++uZbvOg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-glob": "^3.2.11",
"yargs": "^16.2.0"
},
"bin": {
"lcov-result-merger": "bin/lcov-result-merger.js"
},
"engines": {
"node": ">=14"
}
},
"node_modules/lcov-result-merger/node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^7.0.0"
}
},
"node_modules/lcov-result-merger/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==",
"dev": true,
"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/lcov-result-merger/node_modules/yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
"dev": true,
"license": "MIT",
"dependencies": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.0",
"y18n": "^5.0.5",
"yargs-parser": "^20.2.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/lcov-result-merger/node_modules/yargs-parser": {
"version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/levn": { "node_modules/levn": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -4735,6 +5264,16 @@
"dev": true, "dev": true,
"license": "CC0-1.0" "license": "CC0-1.0"
}, },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -4788,6 +5327,16 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mrmime": { "node_modules/mrmime": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
@@ -5041,6 +5590,23 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/pako": { "node_modules/pako": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@@ -5122,6 +5688,30 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC"
},
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
@@ -5306,6 +5896,27 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/react": { "node_modules/react": {
"version": "19.2.0", "version": "19.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
@@ -5482,6 +6093,17 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
"dev": true,
"license": "MIT",
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
}
},
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.53.3", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
@@ -5565,6 +6187,30 @@
"node": ">= 12" "node": ">= 12"
} }
}, },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"queue-microtask": "^1.2.2"
}
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -5718,6 +6364,13 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/stackback": { "node_modules/stackback": {
"version": "0.0.2", "version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -5773,6 +6426,22 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/string-width-cjs": {
"name": "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==",
"dev": true,
"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": { "node_modules/strip-ansi": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -5786,6 +6455,20 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-indent": { "node_modules/strip-indent": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@@ -5881,6 +6564,21 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/test-exclude": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz",
"integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==",
"dev": true,
"license": "ISC",
"dependencies": {
"@istanbuljs/schema": "^0.1.2",
"glob": "^10.4.1",
"minimatch": "^9.0.4"
},
"engines": {
"node": ">=18"
}
},
"node_modules/tinybench": { "node_modules/tinybench": {
"version": "2.9.0", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -6255,6 +6953,46 @@
} }
} }
}, },
"node_modules/vite-plugin-istanbul": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/vite-plugin-istanbul/-/vite-plugin-istanbul-7.2.1.tgz",
"integrity": "sha512-DSPi4ulvYsjnP44sTI5oriNosbM0E6m3uoCxjdxboTtVzxSkFwcDy3/JnSYKebjr+ZToJwVLTms+2CM0rmbbzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/generator": "^7.28.0",
"@istanbuljs/load-nyc-config": "^1.1.0",
"@types/babel__generator": "7.6.8",
"espree": "^10.3.0",
"istanbul-lib-instrument": "^6.0.3",
"picocolors": "^1.1.1",
"source-map": "^0.7.4",
"test-exclude": "^7.0.1"
},
"peerDependencies": {
"vite": ">=4 <=7"
}
},
"node_modules/vite-plugin-istanbul/node_modules/@types/babel__generator": {
"version": "7.6.8",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
"integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.0.0"
}
},
"node_modules/vite-plugin-istanbul/node_modules/source-map": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">= 12"
}
},
"node_modules/vite/node_modules/fsevents": { "node_modules/vite/node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -6476,6 +7214,25 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/wrap-ansi-cjs": {
"name": "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==",
"dev": true,
"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/ws": { "node_modules/ws": {
"version": "8.18.3", "version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+12 -1
View File
@@ -10,7 +10,11 @@
"lint": "eslint .", "lint": "eslint .",
"test": "vitest", "test": "vitest",
"test:coverage": "vitest run --coverage", "test:coverage": "vitest run --coverage",
"test:e2e": "playwright test" "test:e2e": "playwright test",
"coverage:unit": "npm run test:coverage",
"coverage:e2e": "npm run test:e2e",
"coverage:merge": "node scripts/merge-lcov.cjs 'coverage-reports/unit/lcov.info' 'coverage-reports/e2e/coverage.lcov' > coverage-reports/merged/merged.lcov && node scripts/normalize-lcov.cjs coverage-reports/merged/merged.lcov && genhtml coverage-reports/merged/merged.normalized.lcov -o coverage-reports/merged",
"coverage:ci": "npm run coverage:unit && npm run coverage:e2e && npm run coverage:merge"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
@@ -44,15 +48,22 @@
"@vitejs/plugin-react": "^5.1.1", "@vitejs/plugin-react": "^5.1.1",
"@vitest/coverage-v8": "^4.0.14", "@vitest/coverage-v8": "^4.0.14",
"@vitest/ui": "^4.0.14", "@vitest/ui": "^4.0.14",
"cross-env": "^10.1.0",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24", "eslint-plugin-react-refresh": "^0.4.24",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-lib-source-maps": "^5.0.6",
"istanbul-reports": "^3.2.0",
"jsdom": "^27.2.0", "jsdom": "^27.2.0",
"lcov-result-merger": "^5.0.1",
"msw": "^2.12.3", "msw": "^2.12.3",
"rollup-plugin-visualizer": "^6.0.5", "rollup-plugin-visualizer": "^6.0.5",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.48.0", "typescript-eslint": "^8.48.0",
"vite": "^7.2.6", "vite": "^7.2.6",
"vite-plugin-istanbul": "^7.2.1",
"vitest": "^4.0.14" "vitest": "^4.0.14"
} }
} }
+4 -2
View File
@@ -10,7 +10,7 @@ export default defineConfig({
defineCoverageReporterConfig({ defineCoverageReporterConfig({
sourceRoot: process.cwd(), // root used to resolve source files sourceRoot: process.cwd(), // root used to resolve source files
exclude: ['node_modules/**', 'e2e/**'], // exclude test sources and deps exclude: ['node_modules/**', 'e2e/**'], // exclude test sources and deps
resultDir: path.join(process.cwd(), 'e2e-coverage-report'), resultDir: path.join(process.cwd(), 'coverage-reports/e2e'),
reports: [ reports: [
['html'], // interactive HTML at results/e2e-coverage/index.html ['html'], // interactive HTML at results/e2e-coverage/index.html
['lcovonly', { file: 'coverage.lcov' }], // LCOV for CI ['lcovonly', { file: 'coverage.lcov' }], // LCOV for CI
@@ -29,9 +29,11 @@ export default defineConfig({
baseURL: 'http://127.0.0.1:4173', baseURL: 'http://127.0.0.1:4173',
headless: true, headless: true,
trace: 'on-first-retry', trace: 'on-first-retry',
navigationTimeout: 60_000, // 60s for page.goto / navigations
actionTimeout: 30_000, // optional: for clicks/typing etc (default 30s)
}, },
webServer: { webServer: {
command: 'npm run dev -- --host --port 4173', command: 'E2E_COVERAGE=1 npm run dev -- --host --port 4173',
url: 'http://127.0.0.1:4173', url: 'http://127.0.0.1:4173',
timeout: 120 * 1000, timeout: 120 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const files = process.argv.slice(2);
if (files.length < 2) {
console.error('Usage: node merge-lcov.cjs <file1.lcov> <file2.lcov> [<file3.lcov> ...]');
process.exit(2);
}
function ensureEntry(map, sf) {
if (!map.has(sf)) map.set(sf, { other: new Set(), da: new Map() });
return map.get(sf);
}
const combined = new Map();
for (const f of files) {
let content;
try {
content = fs.readFileSync(f, 'utf8');
} catch (err) {
console.error('Could not read', f, err.message);
process.exit(3);
}
const lines = content.split(/\r?\n/);
let currentSF = null;
for (let raw of lines) {
const line = raw.trim();
if (!line) continue;
if (line.startsWith('SF:')) {
currentSF = line.slice(3);
ensureEntry(combined, currentSF);
} else if (!currentSF) {
// ignore lines before first SF
} else if (line.startsWith('DA:')) {
const parts = line.slice(3).split(',');
const lnNum = parseInt(parts[0], 10);
const hitsNum = parseInt(parts[1] || '0', 10);
const entry = ensureEntry(combined, currentSF);
entry.da.set(lnNum, (entry.da.get(lnNum) || 0) + hitsNum);
} else if (line.startsWith('end_of_record')) {
// ignore
} else {
const entry = ensureEntry(combined, currentSF);
entry.other.add(line);
}
}
}
let out = '';
for (const [sf, data] of combined.entries()) {
out += `SF:${sf}\n`;
const other = Array.from(data.other).sort();
for (const l of other) out += l + '\n';
const daEntries = Array.from(data.da.entries()).sort((a, b) => a[0] - b[0]);
for (const [ln, hits] of daEntries) out += `DA:${ln},${hits}\n`;
const lf = daEntries.length;
const lh = daEntries.reduce((acc, [, hits]) => acc + (hits > 0 ? 1 : 0), 0);
out += `LF:${lf}\nLH:${lh}\nend_of_record\n`;
}
process.stdout.write(out);
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env node
// scripts/normalize-lcov.cjs
// Usage: node scripts/normalize-lcov.cjs input.lcov [output.lcov]
const fs = require('fs');
const path = require('path');
const repoRoot = process.cwd();
const inFile = process.argv[2] || 'coverage-reports/merged/merged.lcov';
const outFile = process.argv[3] || inFile.replace(/\.lcov$/, '.normalized.lcov');
if (!fs.existsSync(inFile)) {
console.error('Input file not found:', inFile);
process.exit(2);
}
const content = fs.readFileSync(inFile, 'utf8');
const lines = content.split(/\r?\n/);
// split into records separated by "end_of_record"
const records = [];
let cur = [];
for (const L of lines) {
cur.push(L);
if (L.trim() === 'end_of_record') {
records.push(cur.join('\n'));
cur = [];
}
}
// if file didn't end with end_of_record, push remainder
if (cur.length) records.push(cur.join('\n'));
function exists(p) {
try { return fs.existsSync(p); } catch { return false; }
}
function findInSrc(basename) {
const srcRoot = path.join(repoRoot, 'src');
if (!fs.existsSync(srcRoot)) return null;
// recursive search (stop when found)
function walk(dir) {
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const it of items) {
const full = path.join(dir, it.name);
if (it.isFile() && it.name === basename) return full;
if (it.isDirectory() && it.name !== 'node_modules' && it.name !== '.git') {
const found = walk(full);
if (found) return found;
}
}
return null;
}
return walk(srcRoot);
}
function normalizeSF(sfLine) {
// sfLine is the part after 'SF:'
let sf = sfLine.trim();
// Skip clearly virtual / query-string entries
if (sf.startsWith('@') || sf.includes('?v=') || sf === '@react-refresh') {
return null;
}
// If it's an absolute path that exists, make it repo-relative.
if (path.isAbsolute(sf)) {
if (exists(sf)) return path.relative(repoRoot, sf);
// Try chopping off any leading workspace prefix and look again
const repoName = path.basename(repoRoot);
const idx = sf.indexOf(`/${repoName}/`);
if (idx !== -1) {
const candidate = sf.slice(idx + repoName.length + 2); // after /repoName/
if (exists(path.join(repoRoot, candidate))) return candidate;
if (exists(path.join(repoRoot, 'src', candidate))) return path.posix.join('src', candidate);
}
}
// If it's already relative, check relative to repo root
if (!path.isAbsolute(sf)) {
if (exists(path.join(repoRoot, sf))) return sf;
// If it looks like "stockfill/..." or "<repoName>/..." try to strip the repo prefix
const repoName = path.basename(repoRoot);
if (sf.startsWith(`${repoName}/`) || sf.startsWith('stockfill/')) {
const candidate = sf.replace(new RegExp(`^(${repoName}|stockfill)/`), '');
if (exists(path.join(repoRoot, candidate))) return candidate;
if (exists(path.join(repoRoot, 'src', candidate))) return path.posix.join('src', candidate);
}
// If it is a bare filename "DBProvider.tsx" try src/
if (!sf.includes('/') && exists(path.join(repoRoot, 'src', sf))) {
return path.posix.join('src', sf);
}
}
// If it contains '/src/' somewhere, try to extract the src/... suffix
const srcIdx = sf.indexOf('/src/');
if (srcIdx !== -1) {
const candidate = sf.slice(srcIdx + 1); // remove leading slash
if (exists(path.join(repoRoot, candidate))) return candidate;
}
// Finally, try finding by basename under src
const base = path.basename(sf);
const found = findInSrc(base);
if (found) return path.relative(repoRoot, found);
// give up
return null;
}
let kept = 0, dropped = 0;
const outRecords = records.map(rec => {
// find SF line
const m = rec.match(/^SF:(.*)$/m);
if (!m) {
// no SF line? keep it (unlikely)
kept++;
return rec;
}
const sfRaw = m[1].trim();
const normalized = normalizeSF(sfRaw);
if (!normalized) {
dropped++;
return null; // drop entire record
}
// replace SF:... with normalized relative path using posix separators
const normalizedPosix = normalized.split(path.sep).join(path.posix.sep);
const newRec = rec.replace(/^SF:.*$/m, `SF:${normalizedPosix}`);
kept++;
return newRec;
}).filter(Boolean);
fs.writeFileSync(outFile, outRecords.join('\n') + '\n');
console.log(`Wrote ${outFile} (kept ${kept}, dropped ${dropped} records)`);
+23
View File
@@ -0,0 +1,23 @@
// tests/playwright-collect-coverage.js
const fs = require('fs');
const path = require('path');
const { test } = require('@playwright/test');
test.afterEach(async ({ page }, testInfo) => {
try {
// evaluate coverage from the page
const coverage = await page.evaluate(() => (globalThis.__coverage__ || {}));
if (coverage && Object.keys(coverage).length) {
const dir = path.join(process.cwd(), '.nyc_output');
fs.mkdirSync(dir, { recursive: true });
// unique file per worker/test
const filename = path.join(dir, `coverage-e2e-${process.pid}-${Date.now()}.json`);
fs.writeFileSync(filename, JSON.stringify(coverage));
console.log('Wrote coverage:', filename);
}
} catch (err) {
// don't fail tests if coverage collection fails
console.warn('Failed to collect coverage from page:', err.message || err);
}
});
+35
View File
@@ -0,0 +1,35 @@
// scripts/remap-and-report.cjs
const fs = require('fs');
const path = require('path');
const libCoverage = require('istanbul-lib-coverage');
const libSourceMaps = require('istanbul-lib-source-maps');
const libReport = require('istanbul-lib-report');
const reports = require('istanbul-reports');
const out = libCoverage.createCoverageMap({});
const dir = path.join(process.cwd(), '.nyc_output');
if (!fs.existsSync(dir)) {
console.error('.nyc_output not found');
process.exit(2);
}
const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
for (const f of files) {
const data = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8'));
out.merge(data);
}
// Remap using source maps
const sourceMapStore = libSourceMaps.createSourceMapStore();
const transformed = sourceMapStore.transformCoverage(out);
const remapped = transformed.map;
// Write reports
const outDir = path.join(process.cwd(), 'coverage-reports', 'e2e');
fs.mkdirSync(outDir, { recursive: true });
const context = libReport.createContext({ dir: outDir, coverageMap: remapped });
// lcov + html
reports.create('lcovonly', {}).execute(context);
reports.create('html', {}).execute(context);
console.log('Wrote remapped e2e coverage to', outDir);
+13 -3
View File
@@ -3,12 +3,22 @@ import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react'; import react from '@vitejs/plugin-react';
import { visualizer } from 'rollup-plugin-visualizer'; import { visualizer } from 'rollup-plugin-visualizer';
import path from 'path'; import path from 'path';
import istanbul from 'vite-plugin-istanbul';
const isE2E = Boolean(process.env.E2E_COVERAGE)
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
react(), react(),
visualizer({ filename: 'dist/stats.html', open: false }) visualizer({ filename: 'dist/stats.html', open: false }),
], isE2E &&
istanbul({
include: 'src/**/*',
extension: ['.js', '.ts', '.jsx', '.tsx'],
requireEnv: false, // allow instrumenting unconditionally for CI e2e builds
cypress: true, // enables window.__coverage__ instrumentation style
}),
].filter(Boolean),
resolve: { resolve: {
alias: { alias: {
// force single copies of react/react-dom // force single copies of react/react-dom
@@ -35,7 +45,7 @@ export default defineConfig({
port: 5173 port: 5173
}, },
build: { build: {
sourcemap: false, sourcemap: true,
outDir: 'dist', outDir: 'dist',
// esbuild is lighter. If you want terser for final prod, run it on a beefy CI runner. // esbuild is lighter. If you want terser for final prod, run it on a beefy CI runner.
minify: 'esbuild', minify: 'esbuild',
+24
View File
@@ -0,0 +1,24 @@
import { mergeConfig, defineConfig } from 'vitest/config'
import viteConfig from './vite.config'
export default mergeConfig (
viteConfig,
defineConfig({
test: {
environment: 'jsdom', // or 'jsdom' if you use DOM env
coverage: {
provider: 'v8', // or 'c8' — these support lcov & html
reporter: [
'text',
['lcov', { projectRoot: './' }], // emit SF relative to repo root
'html'
],
reportsDirectory: 'coverage-reports/unit', // ← your new unit folder
// optional:
// all: true,
// include: ['src/**/*.ts'],
// exclude: ['test/**']
},
},
}),
)