From ee91c8734e0e671195257a323f511fa27767d3c8 Mon Sep 17 00:00:00 2001 From: Mengjing Hu Date: Mon, 24 Aug 2026 23:24:33 +0800 Subject: [PATCH 01/15] feat: add canyonjs-dev-report integration and local HTML coverage reporter --- packages/istanbul-lib-report/package.json | 3 ++- packages/istanbul-lib-report/src/index.ts | 3 ++- pnpm-lock.yaml | 22 ++++++++++++++++++ pnpm-workspace.yaml | 4 ++++ scripts/local-html-coverage-reporter.ts | 27 +++++++++++++++++++++++ vitest.config.ts | 5 ++++- 6 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 scripts/local-html-coverage-reporter.ts diff --git a/packages/istanbul-lib-report/package.json b/packages/istanbul-lib-report/package.json index 65453bf..83bd42e 100644 --- a/packages/istanbul-lib-report/package.json +++ b/packages/istanbul-lib-report/package.json @@ -36,7 +36,8 @@ "test": "pnpm --filter @vitest/istanbuljs-monorepo run test --project @vitest/istanbul-lib-report" }, "dependencies": { - "@vitest/istanbul-lib-coverage": "workspace:*" + "@vitest/istanbul-lib-coverage": "workspace:*", + "canyonjs-dev-report": "catalog:" }, "devDependencies": { "@vitest/browser-playwright": "catalog:", diff --git a/packages/istanbul-lib-report/src/index.ts b/packages/istanbul-lib-report/src/index.ts index 76cc843..ede2e7a 100644 --- a/packages/istanbul-lib-report/src/index.ts +++ b/packages/istanbul-lib-report/src/index.ts @@ -21,6 +21,7 @@ import type { CoberturaOptions } from "./reports/cobertura/index"; import HtmlSpaReport from "./reports/html-spa/index"; import type { HtmlSpaOptions } from "./reports/html-spa/index"; import HtmlReport from "./reports/html/index"; +import IstanbulReporterModernHtml from 'canyonjs-dev-report' import type { HtmlOptions } from "./reports/html/index"; import JsonSummaryReport from "./reports/json-summary/index"; import type { JsonSummaryOptions } from "./reports/json-summary/index"; @@ -96,7 +97,7 @@ export { FileWriter }; const reports = { clover: CloverReport, cobertura: CoberturaReport, - html: HtmlReport, + html: IstanbulReporterModernHtml, "html-spa": HtmlSpaReport, json: JsonReport, "json-summary": JsonSummaryReport, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7816157..d85bb57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,9 @@ catalogs: '@vitest/coverage-v8': specifier: 4.1.11 version: 4.1.11 + canyonjs-dev-report: + specifier: ^1.0.16 + version: 1.0.16 changelogithub: specifier: ^15.0.4 version: 15.0.4 @@ -126,6 +129,9 @@ importers: '@vitest/istanbul-lib-coverage': specifier: workspace:* version: link:../istanbul-lib-coverage + canyonjs-dev-report: + specifier: 'catalog:' + version: 1.0.16 devDependencies: '@vitest/browser-playwright': specifier: 'catalog:' @@ -1009,6 +1015,12 @@ packages: caniuse-lite@1.0.30001809: resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + canyonjs-dev-report-html@1.0.16: + resolution: {integrity: sha512-5vBAF83odIdjJGoQ60YxACLaQGcG5fpTRhVE6SFSYMnyUoeykWyAa5/vTdCwU28K4mzE9/IeWdnroqEBUHJ80g==} + + canyonjs-dev-report@1.0.16: + resolution: {integrity: sha512-ihXlTfV/jFBSK4P263U5b+DFiswrDrWkUKMt+TNi4txazInFdKP+AjxBOlYaSLr4g4fiu5KF358hgvaka7nz6g==} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -2387,6 +2399,16 @@ snapshots: caniuse-lite@1.0.30001809: {} + canyonjs-dev-report-html@1.0.16: + optional: true + + canyonjs-dev-report@1.0.16: + dependencies: + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + optionalDependencies: + canyonjs-dev-report-html: 1.0.16 + chai@6.2.2: {} changelogen@0.5.7: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 57e8928..d2405f7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -28,3 +28,7 @@ catalog: typescript: ^7.0.2 "@e18e/eslint-plugin": ^0.8.0 changelogithub: ^15.0.4 + canyonjs-dev-report: ^1.0.16 +minimumReleaseAgeExclude: + - canyonjs-dev-report-html@1.0.16 + - canyonjs-dev-report@1.0.16 diff --git a/scripts/local-html-coverage-reporter.ts b/scripts/local-html-coverage-reporter.ts new file mode 100644 index 0000000..c6b32f0 --- /dev/null +++ b/scripts/local-html-coverage-reporter.ts @@ -0,0 +1,27 @@ +import type { Reporter } from "vitest/node"; + +import { createCoverageMap } from "../packages/istanbul-lib-coverage/src"; +import type { CoverageMapData } from "../packages/istanbul-lib-coverage/src"; +import { create, createContext } from "../packages/istanbul-lib-report/src"; + +function toCoverageMapData(coverage: unknown): CoverageMapData { + if ( + coverage && + typeof coverage === "object" && + "toJSON" in coverage && + typeof coverage.toJSON === "function" + ) { + return coverage.toJSON() as CoverageMapData; + } + return coverage as CoverageMapData; +} + +export default class LocalHtmlCoverageReporter implements Reporter { + onCoverage(coverage: unknown): void { + const context = createContext({ + dir: "coverage", + coverageMap: createCoverageMap(toCoverageMapData(coverage)), + }); + create("html").execute(context); + } +} diff --git a/vitest.config.ts b/vitest.config.ts index 52135a9..63263e7 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,7 +6,10 @@ export default defineConfig({ enabled: true, provider: "v8", include: ["packages/**/src/**/*.ts"], + exclude: ["packages/istanbul-lib-report/src/reports/**"], + reporter: ["json"], }, - projects: ["packages/*/vitest.config.*"], + reporters: ["default", "./scripts/local-html-coverage-reporter.ts"], + projects: ["packages/*"], }, }); From 19e5ad5af74dcc07cc45c7b12fd25d5b7a573979 Mon Sep 17 00:00:00 2001 From: Mengjing Hu Date: Tue, 25 Aug 2026 00:34:18 +0800 Subject: [PATCH 02/15] chore(deps): bump canyonjs-dev-report to ^1.0.18 --- .DS_Store | Bin 0 -> 6148 bytes pnpm-lock.yaml | 20 ++++++++++---------- pnpm-workspace.yaml | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e3ac40b1475050d1cad9d1847c2bab0842303d7c GIT binary patch literal 6148 zcmeHK%}N6?5T4j-Q;N`oqQ`*Og4Gs9yezf8fGc`Xsk?Tmi|eNJ_fQIZ)fe(jd>&_# zELhruClNaX$v2swWaop;4*-bPxO)Up1ptYSP>`}h$h^|E;erW;nPUI}^dJhS!~Tki z{-TM#UBVM+m_P*IzCTQKh`x`opTxsaz5XdS3dPN>k}S!JeDI(3)E@<-@vs#P?r3za zbrR-!5Z=Vmv{T)=(CH|M(`aB4;%PJ8r96DG6T%O&lsTnL182G9A*~H)`1;epUGb&Btf0t5`@a3=P{t@Q4{;2L2fXqS*G^EiB5NtxM(6S*x%;Vk4orj1&~q7cK!fpnYWDIIUli aj&YvD%p%P~c9V|C7Xd>Ecg(;qFz^N59ZahL literal 0 HcmV?d00001 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d85bb57..20f2dfd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,8 +31,8 @@ catalogs: specifier: 4.1.11 version: 4.1.11 canyonjs-dev-report: - specifier: ^1.0.16 - version: 1.0.16 + specifier: ^1.0.18 + version: 1.0.18 changelogithub: specifier: ^15.0.4 version: 15.0.4 @@ -131,7 +131,7 @@ importers: version: link:../istanbul-lib-coverage canyonjs-dev-report: specifier: 'catalog:' - version: 1.0.16 + version: 1.0.18 devDependencies: '@vitest/browser-playwright': specifier: 'catalog:' @@ -1015,11 +1015,11 @@ packages: caniuse-lite@1.0.30001809: resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} - canyonjs-dev-report-html@1.0.16: - resolution: {integrity: sha512-5vBAF83odIdjJGoQ60YxACLaQGcG5fpTRhVE6SFSYMnyUoeykWyAa5/vTdCwU28K4mzE9/IeWdnroqEBUHJ80g==} + canyonjs-dev-report-html@1.0.18: + resolution: {integrity: sha512-oYZHnCfI3cjP7Enz54ZAjUjNl7SxTAoyuPWsKd34kIxh3a4hEMuteh0bKDXHSCe69cAyzl4jMEvxtZWQiMuESQ==} - canyonjs-dev-report@1.0.16: - resolution: {integrity: sha512-ihXlTfV/jFBSK4P263U5b+DFiswrDrWkUKMt+TNi4txazInFdKP+AjxBOlYaSLr4g4fiu5KF358hgvaka7nz6g==} + canyonjs-dev-report@1.0.18: + resolution: {integrity: sha512-mg7GjE18gkMj8ELjqrzmA4N0xfWc1x1Te51zoqWuSiFfzhk+eu9T5tUpqPAncOMoriVwEHb7AWH/SRO9k/Mr0Q==} chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} @@ -2399,15 +2399,15 @@ snapshots: caniuse-lite@1.0.30001809: {} - canyonjs-dev-report-html@1.0.16: + canyonjs-dev-report-html@1.0.18: optional: true - canyonjs-dev-report@1.0.16: + canyonjs-dev-report@1.0.18: dependencies: istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 optionalDependencies: - canyonjs-dev-report-html: 1.0.16 + canyonjs-dev-report-html: 1.0.18 chai@6.2.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d2405f7..cffeda0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -28,7 +28,7 @@ catalog: typescript: ^7.0.2 "@e18e/eslint-plugin": ^0.8.0 changelogithub: ^15.0.4 - canyonjs-dev-report: ^1.0.16 + canyonjs-dev-report: ^1.0.18 minimumReleaseAgeExclude: - - canyonjs-dev-report-html@1.0.16 - - canyonjs-dev-report@1.0.16 + - canyonjs-dev-report-html@1.0.18 + - canyonjs-dev-report@1.0.18 From 13f55d5e4373dbe45cd520faf8ee682643602335 Mon Sep 17 00:00:00 2001 From: Mengjing Hu Date: Tue, 25 Aug 2026 00:35:26 +0800 Subject: [PATCH 03/15] update --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index e3ac40b1475050d1cad9d1847c2bab0842303d7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}N6?5T4j-Q;N`oqQ`*Og4Gs9yezf8fGc`Xsk?Tmi|eNJ_fQIZ)fe(jd>&_# zELhruClNaX$v2swWaop;4*-bPxO)Up1ptYSP>`}h$h^|E;erW;nPUI}^dJhS!~Tki z{-TM#UBVM+m_P*IzCTQKh`x`opTxsaz5XdS3dPN>k}S!JeDI(3)E@<-@vs#P?r3za zbrR-!5Z=Vmv{T)=(CH|M(`aB4;%PJ8r96DG6T%O&lsTnL182G9A*~H)`1;epUGb&Btf0t5`@a3=P{t@Q4{;2L2fXqS*G^EiB5NtxM(6S*x%;Vk4orj1&~q7cK!fpnYWDIIUli aj&YvD%p%P~c9V|C7Xd>Ecg(;qFz^N59ZahL From f8376f574e2ec5970b844d6c869f6f39b2455004 Mon Sep 17 00:00:00 2001 From: Travis Zhang Date: Thu, 27 Aug 2026 00:14:56 +0800 Subject: [PATCH 04/15] chore: remove canyonjs-dev-report integration and related files --- packages/istanbul-lib-report/package.json | 3 +-- packages/istanbul-lib-report/src/index.ts | 3 +-- pnpm-lock.yaml | 22 ------------------ pnpm-workspace.yaml | 6 +---- scripts/local-html-coverage-reporter.ts | 27 ----------------------- vitest.config.ts | 2 -- 6 files changed, 3 insertions(+), 60 deletions(-) delete mode 100644 scripts/local-html-coverage-reporter.ts diff --git a/packages/istanbul-lib-report/package.json b/packages/istanbul-lib-report/package.json index ff4496d..5e83bd9 100644 --- a/packages/istanbul-lib-report/package.json +++ b/packages/istanbul-lib-report/package.json @@ -36,8 +36,7 @@ "test": "pnpm --filter @vitest/istanbuljs-monorepo run test --project @vitest/istanbul-lib-report" }, "dependencies": { - "@vitest/istanbul-lib-coverage": "workspace:*", - "canyonjs-dev-report": "catalog:" + "@vitest/istanbul-lib-coverage": "workspace:*" }, "devDependencies": { "@vitest/browser-playwright": "catalog:", diff --git a/packages/istanbul-lib-report/src/index.ts b/packages/istanbul-lib-report/src/index.ts index ede2e7a..76cc843 100644 --- a/packages/istanbul-lib-report/src/index.ts +++ b/packages/istanbul-lib-report/src/index.ts @@ -21,7 +21,6 @@ import type { CoberturaOptions } from "./reports/cobertura/index"; import HtmlSpaReport from "./reports/html-spa/index"; import type { HtmlSpaOptions } from "./reports/html-spa/index"; import HtmlReport from "./reports/html/index"; -import IstanbulReporterModernHtml from 'canyonjs-dev-report' import type { HtmlOptions } from "./reports/html/index"; import JsonSummaryReport from "./reports/json-summary/index"; import type { JsonSummaryOptions } from "./reports/json-summary/index"; @@ -97,7 +96,7 @@ export { FileWriter }; const reports = { clover: CloverReport, cobertura: CoberturaReport, - html: IstanbulReporterModernHtml, + html: HtmlReport, "html-spa": HtmlSpaReport, json: JsonReport, "json-summary": JsonSummaryReport, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20f2dfd..7816157 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,9 +30,6 @@ catalogs: '@vitest/coverage-v8': specifier: 4.1.11 version: 4.1.11 - canyonjs-dev-report: - specifier: ^1.0.18 - version: 1.0.18 changelogithub: specifier: ^15.0.4 version: 15.0.4 @@ -129,9 +126,6 @@ importers: '@vitest/istanbul-lib-coverage': specifier: workspace:* version: link:../istanbul-lib-coverage - canyonjs-dev-report: - specifier: 'catalog:' - version: 1.0.18 devDependencies: '@vitest/browser-playwright': specifier: 'catalog:' @@ -1015,12 +1009,6 @@ packages: caniuse-lite@1.0.30001809: resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} - canyonjs-dev-report-html@1.0.18: - resolution: {integrity: sha512-oYZHnCfI3cjP7Enz54ZAjUjNl7SxTAoyuPWsKd34kIxh3a4hEMuteh0bKDXHSCe69cAyzl4jMEvxtZWQiMuESQ==} - - canyonjs-dev-report@1.0.18: - resolution: {integrity: sha512-mg7GjE18gkMj8ELjqrzmA4N0xfWc1x1Te51zoqWuSiFfzhk+eu9T5tUpqPAncOMoriVwEHb7AWH/SRO9k/Mr0Q==} - chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -2399,16 +2387,6 @@ snapshots: caniuse-lite@1.0.30001809: {} - canyonjs-dev-report-html@1.0.18: - optional: true - - canyonjs-dev-report@1.0.18: - dependencies: - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - optionalDependencies: - canyonjs-dev-report-html: 1.0.18 - chai@6.2.2: {} changelogen@0.5.7: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index cffeda0..e11566e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -27,8 +27,4 @@ catalog: "@vitest/coverage-v8": 4.1.11 typescript: ^7.0.2 "@e18e/eslint-plugin": ^0.8.0 - changelogithub: ^15.0.4 - canyonjs-dev-report: ^1.0.18 -minimumReleaseAgeExclude: - - canyonjs-dev-report-html@1.0.18 - - canyonjs-dev-report@1.0.18 + changelogithub: ^15.0.4 \ No newline at end of file diff --git a/scripts/local-html-coverage-reporter.ts b/scripts/local-html-coverage-reporter.ts deleted file mode 100644 index c6b32f0..0000000 --- a/scripts/local-html-coverage-reporter.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Reporter } from "vitest/node"; - -import { createCoverageMap } from "../packages/istanbul-lib-coverage/src"; -import type { CoverageMapData } from "../packages/istanbul-lib-coverage/src"; -import { create, createContext } from "../packages/istanbul-lib-report/src"; - -function toCoverageMapData(coverage: unknown): CoverageMapData { - if ( - coverage && - typeof coverage === "object" && - "toJSON" in coverage && - typeof coverage.toJSON === "function" - ) { - return coverage.toJSON() as CoverageMapData; - } - return coverage as CoverageMapData; -} - -export default class LocalHtmlCoverageReporter implements Reporter { - onCoverage(coverage: unknown): void { - const context = createContext({ - dir: "coverage", - coverageMap: createCoverageMap(toCoverageMapData(coverage)), - }); - create("html").execute(context); - } -} diff --git a/vitest.config.ts b/vitest.config.ts index 63263e7..ea17b5b 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,10 +6,8 @@ export default defineConfig({ enabled: true, provider: "v8", include: ["packages/**/src/**/*.ts"], - exclude: ["packages/istanbul-lib-report/src/reports/**"], reporter: ["json"], }, - reporters: ["default", "./scripts/local-html-coverage-reporter.ts"], projects: ["packages/*"], }, }); From 5a42a50ad10a82b9d41e2ce41ebb08a2a0845b4f Mon Sep 17 00:00:00 2001 From: Travis Zhang Date: Thu, 27 Aug 2026 00:48:37 +0800 Subject: [PATCH 05/15] chore: introduce modern HTML coverage report --- .editorconfig | 9 + .gitignore | 3 + html-modern.cjs | 2 + packages/istanbul-lib-report/package.json | 1 + packages/istanbul-lib-report/src/index.ts | 21 + .../src/reports/html-modern/compress.ts | 6 + .../reports/html-modern/coverage-report.ts | 114 ++ .../src/reports/html-modern/index.ts | 61 + .../reports/html-modern/infer-project-root.ts | 73 ++ .../reports/html-modern/istanbul-context.ts | 38 + .../src/reports/html-modern/options.ts | 23 + .../src/reports/html-modern/types.ts | 61 + .../src/reports/html-modern/write-report.ts | 53 + .../html-modern/infer-project-root.test.ts | 43 + .../istanbul-report-html-modern/.gitignore | 5 + .../istanbul-report-html-modern/README.md | 14 + .../istanbul-report-html-modern/index.html | 26 + .../istanbul-report-html-modern/package.json | 75 ++ .../playground/index.html | 23 + .../playground/src/App.tsx | 26 + .../playground/src/index.tsx | 11 + .../playground/src/vite-env.d.ts | 16 + .../report-data-placeholder.ts | 2 + .../src/Report.tsx | 125 ++ .../src/ReportApp.tsx | 53 + .../src/ReportPage.tsx | 41 + .../src/components/CoverageDetail.tsx | 149 +++ .../src/components/LineNumbers.tsx | 57 + .../src/components/ReportFooter.css | 40 + .../src/components/ReportFooter.tsx | 59 + .../src/components/SummaryHeader.tsx | 120 ++ .../src/components/SummaryList.tsx | 95 ++ .../src/components/SummaryTree.tsx | 100 ++ .../src/components/TopControl.tsx | 79 ++ .../istanbul-report-html-modern/src/css.d.ts | 1 + .../src/hash-route.ts | 48 + .../src/helpers/annotate.ts | 165 +++ .../src/helpers/build-report-files.ts | 33 + .../src/helpers/color.ts | 9 + .../src/helpers/decompress.ts | 40 + .../src/helpers/derive-views.ts | 44 + .../src/helpers/empty-coverage.ts | 11 + .../src/helpers/line-hits.ts | 63 + .../src/helpers/loadData.ts | 17 + .../src/helpers/summary.ts | 126 ++ .../src/icons/TreeViewIcon.tsx | 18 + .../istanbul-report-html-modern/src/index.css | 147 +++ .../istanbul-report-html-modern/src/index.ts | 15 + .../istanbul-report-html-modern/src/main.tsx | 14 + .../src/monaco-esm.d.ts | 9 + .../istanbul-report-html-modern/src/monaco.ts | 32 + .../istanbul-report-html-modern/src/paths.ts | 81 ++ .../src/report-data.ts | 38 + .../src/theme-context.tsx | 91 ++ .../istanbul-report-html-modern/src/types.ts | 74 ++ .../src/useHashPath.ts | 30 + .../src/vite-env.d.ts | 3 + .../test/paths.test.ts | 69 + .../test/summary.test.ts | 117 ++ .../istanbul-report-html-modern/tsconfig.json | 19 + .../tsdown.config.ts | 10 + .../vite-plugin-report-data-dev.ts | 31 + .../vite.config.playground.ts | 41 + .../vite.config.ts | 34 + .../vitest.config.ts | 9 + pnpm-lock.yaml | 1167 ++++++++++++++++- pnpm-workspace.yaml | 15 +- vitest.config.ts | 7 +- 68 files changed, 4244 insertions(+), 8 deletions(-) create mode 100644 .editorconfig create mode 100644 html-modern.cjs create mode 100644 packages/istanbul-lib-report/src/reports/html-modern/compress.ts create mode 100644 packages/istanbul-lib-report/src/reports/html-modern/coverage-report.ts create mode 100644 packages/istanbul-lib-report/src/reports/html-modern/index.ts create mode 100644 packages/istanbul-lib-report/src/reports/html-modern/infer-project-root.ts create mode 100644 packages/istanbul-lib-report/src/reports/html-modern/istanbul-context.ts create mode 100644 packages/istanbul-lib-report/src/reports/html-modern/options.ts create mode 100644 packages/istanbul-lib-report/src/reports/html-modern/types.ts create mode 100644 packages/istanbul-lib-report/src/reports/html-modern/write-report.ts create mode 100644 packages/istanbul-lib-report/test/reports/html-modern/infer-project-root.test.ts create mode 100644 packages/istanbul-report-html-modern/.gitignore create mode 100644 packages/istanbul-report-html-modern/README.md create mode 100644 packages/istanbul-report-html-modern/index.html create mode 100644 packages/istanbul-report-html-modern/package.json create mode 100644 packages/istanbul-report-html-modern/playground/index.html create mode 100644 packages/istanbul-report-html-modern/playground/src/App.tsx create mode 100644 packages/istanbul-report-html-modern/playground/src/index.tsx create mode 100644 packages/istanbul-report-html-modern/playground/src/vite-env.d.ts create mode 100644 packages/istanbul-report-html-modern/report-data-placeholder.ts create mode 100644 packages/istanbul-report-html-modern/src/Report.tsx create mode 100644 packages/istanbul-report-html-modern/src/ReportApp.tsx create mode 100644 packages/istanbul-report-html-modern/src/ReportPage.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/CoverageDetail.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/LineNumbers.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/ReportFooter.css create mode 100644 packages/istanbul-report-html-modern/src/components/ReportFooter.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/SummaryHeader.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/SummaryList.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/SummaryTree.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/TopControl.tsx create mode 100644 packages/istanbul-report-html-modern/src/css.d.ts create mode 100644 packages/istanbul-report-html-modern/src/hash-route.ts create mode 100644 packages/istanbul-report-html-modern/src/helpers/annotate.ts create mode 100644 packages/istanbul-report-html-modern/src/helpers/build-report-files.ts create mode 100644 packages/istanbul-report-html-modern/src/helpers/color.ts create mode 100644 packages/istanbul-report-html-modern/src/helpers/decompress.ts create mode 100644 packages/istanbul-report-html-modern/src/helpers/derive-views.ts create mode 100644 packages/istanbul-report-html-modern/src/helpers/empty-coverage.ts create mode 100644 packages/istanbul-report-html-modern/src/helpers/line-hits.ts create mode 100644 packages/istanbul-report-html-modern/src/helpers/loadData.ts create mode 100644 packages/istanbul-report-html-modern/src/helpers/summary.ts create mode 100644 packages/istanbul-report-html-modern/src/icons/TreeViewIcon.tsx create mode 100644 packages/istanbul-report-html-modern/src/index.css create mode 100644 packages/istanbul-report-html-modern/src/index.ts create mode 100644 packages/istanbul-report-html-modern/src/main.tsx create mode 100644 packages/istanbul-report-html-modern/src/monaco-esm.d.ts create mode 100644 packages/istanbul-report-html-modern/src/monaco.ts create mode 100644 packages/istanbul-report-html-modern/src/paths.ts create mode 100644 packages/istanbul-report-html-modern/src/report-data.ts create mode 100644 packages/istanbul-report-html-modern/src/theme-context.tsx create mode 100644 packages/istanbul-report-html-modern/src/types.ts create mode 100644 packages/istanbul-report-html-modern/src/useHashPath.ts create mode 100644 packages/istanbul-report-html-modern/src/vite-env.d.ts create mode 100644 packages/istanbul-report-html-modern/test/paths.test.ts create mode 100644 packages/istanbul-report-html-modern/test/summary.test.ts create mode 100644 packages/istanbul-report-html-modern/tsconfig.json create mode 100644 packages/istanbul-report-html-modern/tsdown.config.ts create mode 100644 packages/istanbul-report-html-modern/vite-plugin-report-data-dev.ts create mode 100644 packages/istanbul-report-html-modern/vite.config.playground.ts create mode 100644 packages/istanbul-report-html-modern/vite.config.ts create mode 100644 packages/istanbul-report-html-modern/vitest.config.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9d08a1a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore index 712822e..abadbef 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ dist lerna-debug.log packages/*/yarn.lock packages/nyc/ + +.DS_Store +.pnpm-store diff --git a/html-modern.cjs b/html-modern.cjs new file mode 100644 index 0000000..7739aa7 --- /dev/null +++ b/html-modern.cjs @@ -0,0 +1,2 @@ +module.exports = + require("./packages/istanbul-lib-report/dist/reports/html-modern/index.mjs").default; diff --git a/packages/istanbul-lib-report/package.json b/packages/istanbul-lib-report/package.json index 5e83bd9..16b79ff 100644 --- a/packages/istanbul-lib-report/package.json +++ b/packages/istanbul-lib-report/package.json @@ -40,6 +40,7 @@ }, "devDependencies": { "@vitest/browser-playwright": "catalog:", + "@vitest/istanbul-report-html-modern": "workspace:*", "playwright": "catalog:", "preact": "catalog:", "tsdown": "catalog:" diff --git a/packages/istanbul-lib-report/src/index.ts b/packages/istanbul-lib-report/src/index.ts index 76cc843..c902ebd 100644 --- a/packages/istanbul-lib-report/src/index.ts +++ b/packages/istanbul-lib-report/src/index.ts @@ -18,6 +18,8 @@ import CloverReport from "./reports/clover/index"; import type { CloverOptions } from "./reports/clover/index"; import CoberturaReport from "./reports/cobertura/index"; import type { CoberturaOptions } from "./reports/cobertura/index"; +import HtmlModernReport from "./reports/html-modern/index"; +import type { HtmlModernOptions } from "./reports/html-modern/index"; import HtmlSpaReport from "./reports/html-spa/index"; import type { HtmlSpaOptions } from "./reports/html-spa/index"; import HtmlReport from "./reports/html/index"; @@ -49,6 +51,23 @@ export type { ReportBaseOptions } from "./report-base"; export type { CloverOptions } from "./reports/clover/index"; export type { CoberturaOptions } from "./reports/cobertura/index"; export type { HtmlSpaOptions } from "./reports/html-spa/index"; +export type { + CoverageData, + CovData, + GenerateOptions, + GenerateResult, + HtmlModernOptions, + IstanbulReportContext, + ReportData, + ReportStats, + SerializableHtmlModernOptions, +} from "./reports/html-modern/index"; +export { + CoverageReport, + extractIstanbulContext, + inferProjectRoot, +} from "./reports/html-modern/index"; +export { default as HtmlModernReport } from "./reports/html-modern/index"; export type { HtmlOptions, LinkMapper } from "./reports/html/index"; export type { JsonSummaryOptions } from "./reports/json-summary/index"; export type { JsonOptions } from "./reports/json/index"; @@ -97,6 +116,7 @@ const reports = { clover: CloverReport, cobertura: CoberturaReport, html: HtmlReport, + "html-modern": HtmlModernReport, "html-spa": HtmlSpaReport, json: JsonReport, "json-summary": JsonSummaryReport, @@ -114,6 +134,7 @@ export interface ReportOptions { clover: CloverOptions; cobertura: CoberturaOptions; html: HtmlOptions; + "html-modern": HtmlModernOptions; "html-spa": HtmlSpaOptions; json: JsonOptions; "json-summary": JsonSummaryOptions; diff --git a/packages/istanbul-lib-report/src/reports/html-modern/compress.ts b/packages/istanbul-lib-report/src/reports/html-modern/compress.ts new file mode 100644 index 0000000..18a1078 --- /dev/null +++ b/packages/istanbul-lib-report/src/reports/html-modern/compress.ts @@ -0,0 +1,6 @@ +import { gzipSync } from "node:zlib"; + +export function compress(jsonstring: string): string { + const gzip = gzipSync(jsonstring, { level: 9 }); + return gzip.toString("base64"); +} diff --git a/packages/istanbul-lib-report/src/reports/html-modern/coverage-report.ts b/packages/istanbul-lib-report/src/reports/html-modern/coverage-report.ts new file mode 100644 index 0000000..85a0ceb --- /dev/null +++ b/packages/istanbul-lib-report/src/reports/html-modern/coverage-report.ts @@ -0,0 +1,114 @@ +import fs from "node:fs"; +import { createRequire } from "node:module"; + +import { resolveProjectRoot } from "./infer-project-root"; +import type { HtmlModernOptions } from "./options"; +import type { + CoverageData, + GenerateOptions, + GenerateResult, + IstanbulReportContext, + ReportData, + SerializableHtmlModernOptions, +} from "./types"; +import { resolveReportHtmlDist, writeHtmlReport, writeReportDataJson } from "./write-report"; + +const require = createRequire(import.meta.url); +const { name: packageName, version: packageVersion } = require("../../../package.json") as { + name: string; + version: string; +}; + +function serializeHtmlOptions(options: HtmlModernOptions): SerializableHtmlModernOptions { + const html: SerializableHtmlModernOptions = {}; + + if (options.verbose !== undefined) { + html.verbose = options.verbose; + } + if (options.subdir !== undefined) { + html.subdir = options.subdir; + } + if (options.skipEmpty !== undefined) { + html.skipEmpty = options.skipEmpty; + } + if (options.metricsToShow !== undefined) { + html.metricsToShow = options.metricsToShow; + } + if (options.projectRoot !== undefined) { + html.projectRoot = options.projectRoot; + } + if (options.writeReportDataJson !== undefined) { + html.writeReportDataJson = options.writeReportDataJson; + } + + return html; +} + +export class CoverageReport { + private htmlOptions: HtmlModernOptions; + + constructor(htmlOptions: HtmlModernOptions = {}) { + this.htmlOptions = htmlOptions; + } + + buildReportData( + coverage: CoverageData, + sourceFinder: (filePath: string) => string, + istanbul: IstanbulReportContext, + ): ReportData { + const sources: Record = {}; + + for (const filePath of Object.keys(coverage)) { + try { + sources[filePath] = sourceFinder(filePath); + } catch { + // skip files whose source cannot be resolved + } + } + + return { + html: serializeHtmlOptions(this.htmlOptions), + istanbul, + stats: { + coverageFileCount: Object.keys(coverage).length, + sourceFileCount: Object.keys(sources).length, + }, + projectRoot: resolveProjectRoot(Object.keys(coverage), this.htmlOptions.projectRoot), + coverage, + sources, + generatedAt: new Date().toISOString(), + packageName, + packageVersion, + }; + } + + async generate({ + coverage, + targetDir, + sourceFinder, + istanbul, + }: GenerateOptions): Promise { + const reportData = this.buildReportData(coverage, sourceFinder, istanbul); + fs.mkdirSync(targetDir, { recursive: true }); + + let htmlReportPath: string | undefined; + const reportHtmlDist = resolveReportHtmlDist(); + if (reportHtmlDist) { + htmlReportPath = writeHtmlReport(reportData, targetDir, reportHtmlDist); + if (this.htmlOptions.verbose) { + console.log(`HTML report written to ${htmlReportPath}`); + } + } else if (this.htmlOptions.verbose) { + console.log("@vitest/istanbul-report-html-modern not found, skipping HTML report generation"); + } + + if (this.htmlOptions.writeReportDataJson) { + const reportDataJsonPath = writeReportDataJson(reportData, targetDir); + if (this.htmlOptions.verbose) { + console.log(`Report data written to ${reportDataJsonPath}`); + } + } + + return htmlReportPath !== undefined ? { reportData, htmlReportPath } : { reportData }; + } +} diff --git a/packages/istanbul-lib-report/src/reports/html-modern/index.ts b/packages/istanbul-lib-report/src/reports/html-modern/index.ts new file mode 100644 index 0000000..34b2b11 --- /dev/null +++ b/packages/istanbul-lib-report/src/reports/html-modern/index.ts @@ -0,0 +1,61 @@ +/* + Copyright 2012-2015, Yahoo Inc. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ +import type { FileCoverageData } from "@vitest/istanbul-lib-coverage"; + +import type { Context, ReportBaseOptions, ReportNode, Summarizers } from "../../index"; +import ReportBase from "../../report-base"; +import { CoverageReport } from "./coverage-report"; +import { extractIstanbulContext } from "./istanbul-context"; +import type { HtmlModernOptions } from "./options"; + +export type { LinkMapper } from "../html/index"; +export type { HtmlModernOptions } from "./options"; +export type { + CoverageData, + CovData, + GenerateOptions, + GenerateResult, + IstanbulReportContext, + ReportData, + ReportStats, + SerializableHtmlModernOptions, +} from "./types"; +export { CoverageReport } from "./coverage-report"; +export { extractIstanbulContext } from "./istanbul-context"; +export { inferProjectRoot, resolveProjectRoot } from "./infer-project-root"; + +class HtmlModernReport extends ReportBase { + private options: HtmlModernOptions; + private coverage: Record = {}; + private summarizer?: Summarizers; + + constructor(opts: HtmlModernOptions & Partial = {}) { + super(opts); + this.options = opts; + this.coverage = {}; + if (opts.summarizer !== undefined) { + this.summarizer = opts.summarizer; + } + } + + onDetail(node: ReportNode): void { + const fileCoverage: FileCoverageData = JSON.parse( + JSON.stringify(node.getFileCoverage().toJSON()), + ); + this.coverage[fileCoverage.path] = fileCoverage; + } + + async onEnd(_rootNode: ReportNode, context: Context): Promise { + const cr = new CoverageReport(this.options); + await cr.generate({ + coverage: this.coverage, + targetDir: context.dir, + sourceFinder: context.sourceFinder, + istanbul: extractIstanbulContext(context, this.summarizer), + }); + } +} + +export default HtmlModernReport; diff --git a/packages/istanbul-lib-report/src/reports/html-modern/infer-project-root.ts b/packages/istanbul-lib-report/src/reports/html-modern/infer-project-root.ts new file mode 100644 index 0000000..7bb8e99 --- /dev/null +++ b/packages/istanbul-lib-report/src/reports/html-modern/infer-project-root.ts @@ -0,0 +1,73 @@ +/** Parent directory with POSIX slashes (empty string if none / only filesystem root). */ +function posixParentDir(filePath: string): string { + const posix = filePath.replaceAll("\\", "/").replace(/\/+$/, ""); + const idx = posix.lastIndexOf("/"); + // Match SummarizerFactory: file directly under `/` has an empty parent path. + if (idx <= 0) { + return ""; + } + return posix.slice(0, idx); +} + +function commonPrefixSegments(paths: string[]): string[] { + if (paths.length === 0) { + return []; + } + + const segmentsList = paths.map((p) => { + if (p === "") { + return []; + } + if (p === "/") { + return [""]; + } + return p.split("/"); + }); + + let common = segmentsList[0]!; + for (let i = 1; i < segmentsList.length; i++) { + const other = segmentsList[i]!; + const next: string[] = []; + const len = Math.min(common.length, other.length); + for (let j = 0; j < len; j++) { + if (common[j] === other[j]) { + next.push(common[j]!); + } else { + break; + } + } + common = next; + if (common.length === 0) { + break; + } + } + + return common; +} + +/** + * Infer project root like SummarizerFactory's `_commonParent`: + * the common parent directory of all coverage file paths. + * Returns `undefined` when there is no useful shared prefix (including filesystem root only). + */ +export function inferProjectRoot(filePaths: string[]): string | undefined { + if (filePaths.length === 0) { + return undefined; + } + + const common = commonPrefixSegments(filePaths.map(posixParentDir)); + // Empty or only `""` (POSIX `/`) is not a useful project root for relativizing paths. + if (common.length === 0 || (common.length === 1 && common[0] === "")) { + return undefined; + } + + return common.join("/"); +} + +export function resolveProjectRoot(filePaths: string[], explicit?: string): string { + if (explicit !== undefined && explicit !== "") { + return explicit; + } + + return inferProjectRoot(filePaths) || process.cwd(); +} diff --git a/packages/istanbul-lib-report/src/reports/html-modern/istanbul-context.ts b/packages/istanbul-lib-report/src/reports/html-modern/istanbul-context.ts new file mode 100644 index 0000000..eeea143 --- /dev/null +++ b/packages/istanbul-lib-report/src/reports/html-modern/istanbul-context.ts @@ -0,0 +1,38 @@ +import type { Context, Summarizers } from "../../index"; +import type { IstanbulReportContext } from "./types"; + +interface ContextWithSummarizerFactory { + _summarizerFactory?: { + _defaultSummarizer: Summarizers; + }; +} + +function getDefaultSummarizer(context: Context): Summarizers { + const factory = (context as unknown as ContextWithSummarizerFactory)._summarizerFactory; + return factory?._defaultSummarizer ?? "pkg"; +} + +function getSourceFinderKind(sourceFinder: (filePath: string) => string): "filesystem" | "custom" { + const { name } = sourceFinder; + if (name === "defaultSourceLookup" || name === "bound defaultSourceLookup") { + return "filesystem"; + } + + return "custom"; +} + +/** extract serializable istanbul context fields for the HTML report */ +export function extractIstanbulContext( + context: Context, + summarizer?: Summarizers, +): IstanbulReportContext { + const defaultSummarizer = getDefaultSummarizer(context); + + return { + dir: context.dir, + watermarks: context.watermarks, + defaultSummarizer, + sourceFinder: getSourceFinderKind(context.sourceFinder), + ...(summarizer !== undefined ? { summarizer } : {}), + }; +} diff --git a/packages/istanbul-lib-report/src/reports/html-modern/options.ts b/packages/istanbul-lib-report/src/reports/html-modern/options.ts new file mode 100644 index 0000000..6b48a52 --- /dev/null +++ b/packages/istanbul-lib-report/src/reports/html-modern/options.ts @@ -0,0 +1,23 @@ +import type { LinkMapper } from "../html/index"; + +/** options accepted by {@link HtmlModernReport} */ +export interface HtmlModernOptions { + /** show extra logging while the report is generated */ + verbose?: boolean; + /** maps report nodes to output paths (reserved; not used by the modern single-file writer) */ + linkMapper?: LinkMapper; + /** subdirectory under the report dir (reserved for parity with classic html) */ + subdir?: string; + /** skip nodes with no coverage */ + skipEmpty?: boolean; + /** the metrics to show in the report UI, defaults to lines, branches and functions */ + metricsToShow?: ("lines" | "branches" | "functions" | "statements")[]; + /** + * Project root used to relativize coverage paths in the UI. + * Defaults to the common parent of coverage file directories (same idea as + * SummarizerFactory `_commonParent`), falling back to `process.cwd()`. + */ + projectRoot?: string; + /** write `report-data.json` alongside `index.html` in the report output directory */ + writeReportDataJson?: boolean; +} diff --git a/packages/istanbul-lib-report/src/reports/html-modern/types.ts b/packages/istanbul-lib-report/src/reports/html-modern/types.ts new file mode 100644 index 0000000..f549582 --- /dev/null +++ b/packages/istanbul-lib-report/src/reports/html-modern/types.ts @@ -0,0 +1,61 @@ +import type { FileCoverageData } from "@vitest/istanbul-lib-coverage"; + +import type { Summarizers, Watermarks } from "../../index"; +import type { HtmlModernOptions } from "./options"; + +/** coverage data keyed by absolute file path */ +export type CoverageData = Record; + +/** html report options embedded in the HTML report (`linkMapper` is omitted) */ +export type SerializableHtmlModernOptions = Omit; + +/** serializable subset of istanbul `createContext` options plus report tree config */ +export interface IstanbulReportContext { + /** output directory from {@link Context.dir} */ + dir: string; + /** low/high percentage thresholds from {@link Context.watermarks} */ + watermarks: Watermarks; + /** default tree from `createContext({ defaultSummarizer })` */ + defaultSummarizer: Summarizers; + /** tree chosen by this report via `ReportBase` `summarizer` option */ + summarizer?: Summarizers; + /** whether context uses istanbul's filesystem lookup or a custom `sourceFinder` */ + sourceFinder: "filesystem" | "custom"; +} + +/** summary counts shown in the HTML report UI */ +export interface ReportStats { + coverageFileCount: number; + sourceFileCount: number; +} + +/** input to {@link CoverageReport.generate} */ +export interface GenerateOptions { + coverage: CoverageData; + targetDir: string; + sourceFinder: (filePath: string) => string; + istanbul: IstanbulReportContext; +} + +/** serialized payload embedded in the HTML report */ +export interface ReportData { + html: SerializableHtmlModernOptions; + istanbul: IstanbulReportContext; + stats: ReportStats; + /** project root used to relativize coverage paths in the UI (inferred or explicit) */ + projectRoot: string; + coverage: CoverageData; + sources: Record; + generatedAt?: string; + packageName?: string; + packageVersion?: string; +} + +/** @deprecated use {@link ReportData} */ +export type CovData = ReportData; + +/** output from {@link CoverageReport.generate} */ +export interface GenerateResult { + reportData: ReportData; + htmlReportPath?: string; +} diff --git a/packages/istanbul-lib-report/src/reports/html-modern/write-report.ts b/packages/istanbul-lib-report/src/reports/html-modern/write-report.ts new file mode 100644 index 0000000..002cac8 --- /dev/null +++ b/packages/istanbul-lib-report/src/reports/html-modern/write-report.ts @@ -0,0 +1,53 @@ +import fs from "node:fs"; +import { createRequire } from "node:module"; +import path from "node:path"; + +import { compress } from "./compress"; +import type { ReportData } from "./types"; + +const require = createRequire(import.meta.url); + +/** Must stay in sync with packages/istanbul-report-html-modern/report-data-placeholder.ts */ +export const REPORT_DATA_PLACEHOLDER = "__REPORT_DATA__"; + +export function resolveReportHtmlDist(): string | null { + try { + const packageJsonPath = require.resolve("@vitest/istanbul-report-html-modern/package.json"); + const distDir = path.join(path.dirname(packageJsonPath), "dist"); + if (fs.existsSync(path.join(distDir, "index.html"))) { + return distDir; + } + } catch { + // @vitest/istanbul-report-html-modern is not installed or dist is missing + } + + return null; +} + +function serializeReportData(reportData: ReportData): string { + return `'${compress(JSON.stringify(reportData))}'`; +} + +export function writeReportDataJson(reportData: ReportData, targetDir: string): string { + const reportDataJsonPath = path.join(targetDir, "report-data.json"); + fs.writeFileSync(reportDataJsonPath, JSON.stringify(reportData, null, 2), "utf-8"); + + return reportDataJsonPath; +} + +export function writeHtmlReport( + reportData: ReportData, + targetDir: string, + reportHtmlDist: string, +): string { + const template = fs.readFileSync(path.join(reportHtmlDist, "index.html"), "utf-8"); + if (!template.includes(REPORT_DATA_PLACEHOLDER)) { + throw new Error(`HTML template missing ${REPORT_DATA_PLACEHOLDER} placeholder`); + } + + const html = template.replace(REPORT_DATA_PLACEHOLDER, serializeReportData(reportData)); + const htmlReportPath = path.join(targetDir, "index.html"); + fs.writeFileSync(htmlReportPath, html, "utf-8"); + + return htmlReportPath; +} diff --git a/packages/istanbul-lib-report/test/reports/html-modern/infer-project-root.test.ts b/packages/istanbul-lib-report/test/reports/html-modern/infer-project-root.test.ts new file mode 100644 index 0000000..250de5a --- /dev/null +++ b/packages/istanbul-lib-report/test/reports/html-modern/infer-project-root.test.ts @@ -0,0 +1,43 @@ +import { assert, describe, it } from "vitest"; + +import { inferProjectRoot } from "../../../src/reports/html-modern/infer-project-root"; + +describe("inferProjectRoot", () => { + it("returns the common parent of coverage file directories", () => { + assert.equal( + inferProjectRoot([ + "/Users/me/proj/src/a.ts", + "/Users/me/proj/src/b.ts", + "/Users/me/proj/lib/c.ts", + ]), + "/Users/me/proj", + ); + }); + + it("returns the shared directory when all files share one folder", () => { + assert.equal( + inferProjectRoot(["/Users/me/proj/src/a.ts", "/Users/me/proj/src/b.ts"]), + "/Users/me/proj/src", + ); + }); + + it("keeps a shallow common parent when only the top segment matches", () => { + assert.equal(inferProjectRoot(["/Users/a/x.ts", "/Users/b/y.ts"]), "/Users"); + }); + + it("returns undefined when there is no common parent", () => { + assert.equal(inferProjectRoot(["/Users/a/x.ts", "/opt/b/y.ts"]), undefined); + assert.equal(inferProjectRoot(["/foo.ts", "/bar.ts"]), undefined); + }); + + it("returns undefined for an empty list", () => { + assert.equal(inferProjectRoot([]), undefined); + }); + + it("normalizes Windows separators", () => { + assert.equal( + inferProjectRoot(["C:\\Users\\me\\proj\\src\\a.ts", "C:\\Users\\me\\proj\\lib\\b.ts"]), + "C:/Users/me/proj", + ); + }); +}); diff --git a/packages/istanbul-report-html-modern/.gitignore b/packages/istanbul-report-html-modern/.gitignore new file mode 100644 index 0000000..77db5ae --- /dev/null +++ b/packages/istanbul-report-html-modern/.gitignore @@ -0,0 +1,5 @@ +node_modules +dist +*.log +.DS_Store +coverage diff --git a/packages/istanbul-report-html-modern/README.md b/packages/istanbul-report-html-modern/README.md new file mode 100644 index 0000000..30e4bf3 --- /dev/null +++ b/packages/istanbul-report-html-modern/README.md @@ -0,0 +1,14 @@ +# @vitest/istanbul-report-html-modern + +Modern Istanbul HTML coverage report: + +- **React components** — `import { ReportApp } from "@vitest/istanbul-report-html-modern"` + `import "@vitest/istanbul-report-html-modern/style.css"` +- **Single-file HTML** — `dist/index.html` (used by `@vitest/istanbul-lib-report`’s `html-modern` report) + +## Scripts + +```bash +pnpm build # dist/index.js + style.css + index.html +pnpm dev # full report page with mock data +pnpm play # UI playground +``` diff --git a/packages/istanbul-report-html-modern/index.html b/packages/istanbul-report-html-modern/index.html new file mode 100644 index 0000000..38a932c --- /dev/null +++ b/packages/istanbul-report-html-modern/index.html @@ -0,0 +1,26 @@ + + + + + + report-html + + + +
+ + + + diff --git a/packages/istanbul-report-html-modern/package.json b/packages/istanbul-report-html-modern/package.json new file mode 100644 index 0000000..e138c62 --- /dev/null +++ b/packages/istanbul-report-html-modern/package.json @@ -0,0 +1,75 @@ +{ + "name": "@vitest/istanbul-report-html-modern", + "version": "1.0.0", + "description": "Modern Istanbul HTML coverage report: React UI + single-file HTML page", + "keywords": [ + "coverage", + "html", + "istanbul", + "react", + "report", + "vitest" + ], + "homepage": "https://vitest.dev/", + "bugs": { + "url": "https://github.com/vitest-dev/vitest/issues" + }, + "license": "MIT", + "author": "Travis Zhang", + "repository": { + "type": "git", + "url": "git+https://github.com/vitest-dev/istanbuljs.git", + "directory": "packages/istanbul-report-html-modern" + }, + "files": [ + "dist" + ], + "type": "module", + "sideEffects": [ + "**/*.css" + ], + "exports": { + ".": "./dist/index.js", + "./package.json": "./package.json", + "./style.css": "./dist/style.css" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "pnpm run build:lib && pnpm run build:page", + "build:lib": "tsdown", + "build:page": "vite build", + "dev": "vite", + "play": "vite --config vite.config.playground.ts", + "typecheck": "tsc --noEmit", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/icons": "catalog:", + "antd": "catalog:", + "monaco-editor": "catalog:", + "react-highlight-words": "catalog:" + }, + "devDependencies": { + "@tsdown/css": "catalog:", + "@types/node": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "@types/react-highlight-words": "catalog:", + "@vitejs/plugin-react": "catalog:", + "react": "catalog:", + "react-dom": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plugin-singlefile": "catalog:" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "engines": { + "node": ">=22" + } +} diff --git a/packages/istanbul-report-html-modern/playground/index.html b/packages/istanbul-report-html-modern/playground/index.html new file mode 100644 index 0000000..eb29b28 --- /dev/null +++ b/packages/istanbul-report-html-modern/playground/index.html @@ -0,0 +1,23 @@ + + + + + + Coverage Report + + +
+ + + + diff --git a/packages/istanbul-report-html-modern/playground/src/App.tsx b/packages/istanbul-report-html-modern/playground/src/App.tsx new file mode 100644 index 0000000..68a3802 --- /dev/null +++ b/packages/istanbul-report-html-modern/playground/src/App.tsx @@ -0,0 +1,26 @@ +import reportData from "@repo/fixtures/report-data.json"; +import { useMemo } from "react"; + +import { buildReportFiles } from "../../src/helpers/build-report-files"; +import { ReportApp } from "../../src/ReportApp"; +import type { FileCoverageData } from "../../src/types"; + +export function App() { + const prepared = useMemo( + () => + buildReportFiles({ + projectRoot: reportData.projectRoot, + coverage: reportData.coverage as Record, + sources: reportData.sources, + }), + [], + ); + + return ( + + ); +} diff --git a/packages/istanbul-report-html-modern/playground/src/index.tsx b/packages/istanbul-report-html-modern/playground/src/index.tsx new file mode 100644 index 0000000..8480740 --- /dev/null +++ b/packages/istanbul-report-html-modern/playground/src/index.tsx @@ -0,0 +1,11 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; + +import { App } from "./App.tsx"; +import "monaco-editor-css"; + +createRoot(document.querySelector("#app")!).render( + + + , +); diff --git a/packages/istanbul-report-html-modern/playground/src/vite-env.d.ts b/packages/istanbul-report-html-modern/playground/src/vite-env.d.ts new file mode 100644 index 0000000..e901c96 --- /dev/null +++ b/packages/istanbul-report-html-modern/playground/src/vite-env.d.ts @@ -0,0 +1,16 @@ +declare module "monaco-editor-css" {} + +declare module "@repo/fixtures/report-data.json" { + const reportData: { + html: Record; + istanbul: Record; + stats: { + coverageFileCount: number; + sourceFileCount: number; + }; + projectRoot: string; + coverage: Record; + sources: Record; + }; + export default reportData; +} diff --git a/packages/istanbul-report-html-modern/report-data-placeholder.ts b/packages/istanbul-report-html-modern/report-data-placeholder.ts new file mode 100644 index 0000000..b56c352 --- /dev/null +++ b/packages/istanbul-report-html-modern/report-data-placeholder.ts @@ -0,0 +1,2 @@ +/** Placeholder replaced at report generation time; keep in sync with coverage-report.ts */ +export const REPORT_DATA_PLACEHOLDER = "__REPORT_DATA__"; diff --git a/packages/istanbul-report-html-modern/src/Report.tsx b/packages/istanbul-report-html-modern/src/Report.tsx new file mode 100644 index 0000000..4aad74c --- /dev/null +++ b/packages/istanbul-report-html-modern/src/Report.tsx @@ -0,0 +1,125 @@ +import { ConfigProvider, Spin, theme as antdTheme } from "antd"; +import { type FC, useEffect, useId, useMemo, useRef, useState } from "react"; + +import CoverageDetail from "./components/CoverageDetail"; +import SummaryHeader from "./components/SummaryHeader"; +import SummaryList from "./components/SummaryList"; +import SummaryTree from "./components/SummaryTree"; +import TopControl from "./components/TopControl"; +import { deriveSummaryViews } from "./helpers/derive-views"; +import { emptyFileCoverage } from "./helpers/empty-coverage"; +import { ThemeProvider, useTheme } from "./theme-context"; +import type { FileCoverageData, ReportProps } from "./types"; + +const ReportContent: FC = ({ value, name, dataSource, onSelect }) => { + const { theme } = useTheme(); + const [isLoading, setIsLoading] = useState(false); + const [filenameKeywords, setFilenameKeywords] = useState(""); + const [showMode, setShowMode] = useState("tree"); + const [fileCoverage, setFileCoverage] = useState(emptyFileCoverage); + const [fileContent, setFileContent] = useState(""); + const rootId = useId().replaceAll(":", ""); + const rootClassName = `report-scope-${rootId} istanbul-html-modern`; + const onSelectRef = useRef(onSelect); + onSelectRef.current = onSelect; + + const requestSelect = (path: string) => { + void onSelectRef.current(path); + }; + + useEffect(() => { + let cancelled = false; + setIsLoading(true); + void onSelectRef + .current(value) + .then((res) => { + if (cancelled) { + return; + } + setFileContent(res.fileContent); + setFileCoverage(res.fileCoverage); + }) + .finally(() => { + if (!cancelled) { + setIsLoading(false); + } + }); + return () => { + cancelled = true; + }; + }, [value]); + + const isFile = useMemo(() => dataSource.some((item) => item.path === value), [dataSource, value]); + const mode = isFile ? "file" : showMode; + const isFileDataReady = isFile && !isLoading; + + const { treeDataSource, rootDataSource, listDataSource } = useMemo( + () => + deriveSummaryViews({ + dataSource, + filenameKeywords, + value, + }), + [dataSource, value, filenameKeywords], + ); + + return ( + +
+ + + + {mode === "file" ? ( +
+ + {isFileDataReady && ( + + )} + +
+ ) : ( +
+ {mode === "tree" && ( + + )} + {mode === "list" && ( + + )} +
+ )} +
+
+ ); +}; + +export const Report: FC = (props) => ( + + + +); + +export default Report; +export type { ReportProps }; diff --git a/packages/istanbul-report-html-modern/src/ReportApp.tsx b/packages/istanbul-report-html-modern/src/ReportApp.tsx new file mode 100644 index 0000000..d74905b --- /dev/null +++ b/packages/istanbul-report-html-modern/src/ReportApp.tsx @@ -0,0 +1,53 @@ +import { type FC, useCallback, useMemo } from "react"; + +import { emptyFileCoverage } from "./helpers/empty-coverage"; +import { filesToDataSource } from "./helpers/summary"; +import { projectRootBaseName, toRelativePath } from "./paths"; +import { Report } from "./Report"; +import type { FileDataResponse, ReportAppProps } from "./types"; +import { useHashPath } from "./useHashPath"; + +export const ReportApp: FC = ({ + files = [], + projectRoot, + name, + defaultValue = "", +}) => { + const [value, setValue] = useHashPath(defaultValue); + + const relativeFiles = useMemo(() => { + return files.map((item) => ({ + ...item, + path: toRelativePath(item.path, projectRoot), + })); + }, [files, projectRoot]); + + const dataSource = useMemo(() => filesToDataSource(relativeFiles), [relativeFiles]); + const reportName = name ?? projectRootBaseName(projectRoot); + + const onSelect = useCallback( + (val: string): Promise => { + setValue(val); + const file = relativeFiles.find((item) => item.path === val); + if (file === undefined) { + return Promise.resolve({ + fileCoverage: emptyFileCoverage, + fileContent: "", + }); + } + return Promise.resolve({ + fileCoverage: file, + fileContent: file.source, + }); + }, + [relativeFiles, setValue], + ); + + return ( +
+ +
+ ); +}; + +export default ReportApp; diff --git a/packages/istanbul-report-html-modern/src/ReportPage.tsx b/packages/istanbul-report-html-modern/src/ReportPage.tsx new file mode 100644 index 0000000..f368d64 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/ReportPage.tsx @@ -0,0 +1,41 @@ +import { useMemo } from "react"; + +import { ReportFooter } from "./components/ReportFooter"; +import { buildReportFiles } from "./helpers/build-report-files"; +import { ReportApp } from "./ReportApp"; + +import "./index.css"; + +/** Standalone single-file HTML page shell around {@link ReportApp}. */ +export function ReportPage() { + const reportData = window.reportData; + + const prepared = useMemo(() => { + if (reportData === undefined) { + return null; + } + return buildReportFiles(reportData); + }, [reportData]); + console.log({ + prepared, + reportData, + }); + if (prepared === null || reportData === undefined) { + return

No report data loaded.

; + } + + return ( +
+
+ +
+ +
+ ); +} + +export default ReportPage; diff --git a/packages/istanbul-report-html-modern/src/components/CoverageDetail.tsx b/packages/istanbul-report-html-modern/src/components/CoverageDetail.tsx new file mode 100644 index 0000000..25fdb0f --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/CoverageDetail.tsx @@ -0,0 +1,149 @@ +import { useEffect, useMemo, useRef } from "react"; + +import { annotateBranches, annotateFunctions, annotateStatements } from "../helpers/annotate"; +import type { CoverageAnnotation } from "../helpers/annotate"; +import { emptyFileCoverage } from "../helpers/empty-coverage"; +import { computeLineHits } from "../helpers/line-hits"; +import { languageFromPath, monaco } from "../monaco"; +import type { ThemeMode } from "../theme-context"; +import type { FileCoverageData } from "../types"; +import { renderLineNumberGutter } from "./LineNumbers"; + +const UNCOVERED_HOVER: Record = { + S: "Statement not covered", + F: "Function not covered", + B: "Branch not covered", + I: "If path not taken", + E: "Else path not taken", +}; + +function hoverMessage(type: CoverageAnnotation["type"]): monaco.IMarkdownString { + return { value: UNCOVERED_HOVER[type] }; +} + +const CoverageDetail = ({ + source, + coverage, + theme, +}: { + source: string; + coverage: FileCoverageData; + theme: ThemeMode; +}) => { + const fileCoverage = coverage.path === "" ? emptyFileCoverage : coverage; + const { lines } = useMemo(() => computeLineHits(fileCoverage, source), [fileCoverage, source]); + const ref = useRef(null); + const editorRef = useRef | null>(null); + + const linesState = useMemo( + () => + lines.map((line, index) => ({ + lineNumber: index + 1, + hit: line.executionNumber, + })), + [lines], + ); + + const lineNumbersMinChars = useMemo(() => { + const maxHit = Math.max(0, ...linesState.map((line) => line.hit)); + return maxHit.toString().length + 7; + }, [linesState]); + + useEffect(() => { + const dom = ref.current; + if (dom === null) { + return; + } + + const monacoTheme = theme === "dark" ? "vs-dark" : "vs"; + + const editor = monaco.editor.create(dom, { + value: source, + language: languageFromPath(fileCoverage.path), + theme: monacoTheme, + fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", + lineHeight: 18, + lineNumbers: (lineNumber: number) => renderLineNumberGutter(lineNumber, linesState), + lineNumbersMinChars, + readOnly: true, + folding: false, + minimap: { enabled: false }, + scrollBeyondLastLine: false, + showUnused: false, + fontSize: 12, + contextmenu: false, + automaticLayout: true, + links: false, + quickSuggestions: false, + wordBasedSuggestions: "off", + }); + editorRef.current = editor; + + const all = [ + ...annotateStatements(fileCoverage, source), + ...annotateFunctions(fileCoverage, source), + ...annotateBranches(fileCoverage, source), + ]; + + const decorations: monaco.editor.IModelDeltaDecoration[] = []; + for (const item of all) { + const hover = hoverMessage(item.type); + if (item.type === "S" || item.type === "F") { + decorations.push({ + range: new monaco.Range(item.startLine, item.startCol, item.endLine, item.endCol), + options: { + isWholeLine: false, + inlineClassName: "content-class-no-found", + hoverMessage: hover, + }, + }); + } else if (item.type === "B") { + decorations.push({ + range: new monaco.Range(item.startLine, item.startCol, item.endLine, item.endCol), + options: { + isWholeLine: false, + inlineClassName: "content-class-no-found-branch", + hoverMessage: hover, + }, + }); + } else if (item.type === "I") { + decorations.push({ + range: new monaco.Range(item.startLine, item.startCol, item.startLine, item.startCol), + options: { + beforeContentClassName: "insert-i-decoration", + hoverMessage: hover, + stickiness: monaco.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + }, + }); + } else if (item.type === "E") { + decorations.push({ + range: new monaco.Range(item.startLine, item.startCol, item.startLine, item.startCol), + options: { + beforeContentClassName: "insert-e-decoration", + hoverMessage: hover, + stickiness: monaco.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + }, + }); + } + } + + editor.createDecorationsCollection(decorations); + + return () => { + editor.dispose(); + editorRef.current = null; + }; + }, [source, fileCoverage, linesState, lineNumbersMinChars, theme]); + + useEffect(() => { + monaco.editor.setTheme(theme === "dark" ? "vs-dark" : "vs"); + }, [theme]); + + return ( +
+
+
+ ); +}; + +export default CoverageDetail; diff --git a/packages/istanbul-report-html-modern/src/components/LineNumbers.tsx b/packages/istanbul-report-html-modern/src/components/LineNumbers.tsx new file mode 100644 index 0000000..fae538e --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/LineNumbers.tsx @@ -0,0 +1,57 @@ +import { renderToStaticMarkup } from "react-dom/server"; + +interface LineState { + lineNumber: number; + hit: number; +} + +function hitBackground(hit: number): string { + if (hit > 0) { + return "var(--report-line-hit-covered)"; + } + if (hit === 0) { + return "var(--report-line-hit-uncovered)"; + } + return "var(--report-line-hit-neutral)"; +} + +function LineNumberWrapper({ + lineNumber, + line, + maxHitWidth, +}: { + lineNumber: number; + line: LineState; + maxHitWidth: number; +}) { + return ( +
+ {lineNumber} + + {line.hit > 0 ? `${line.hit}x` : ""} + +
+ ); +} + +/** Render Monaco line-number gutter HTML for a single line. */ +export function renderLineNumberGutter(lineNumber: number, linesState: LineState[]): string { + const line = linesState.find((item) => item.lineNumber === lineNumber) ?? { + hit: -1, + lineNumber, + }; + + const maxHit = Math.max(0, ...linesState.map((item) => item.hit)); + const digitWidth = maxHit.toString().length; + const maxHitWidth = (digitWidth + 2) * 7.2; + + return renderToStaticMarkup( + , + ); +} diff --git a/packages/istanbul-report-html-modern/src/components/ReportFooter.css b/packages/istanbul-report-html-modern/src/components/ReportFooter.css new file mode 100644 index 0000000..c151c96 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/ReportFooter.css @@ -0,0 +1,40 @@ +.report-page { + height: 100%; + display: flex; + flex-direction: column; +} + +.report-page__content { + flex: 1; + min-height: 0; + overflow: hidden; +} + +.report-footer { + flex-shrink: 0; + padding: 8px 16px 6px; + text-align: center; + font-size: 12px; + line-height: 1.6; + color: var(--report-line-coverage-text); + border-top: 1px solid var(--report-line-coverage-bg); +} + +.report-footer__meta { + margin: 0; +} + +.report-footer a { + color: var(--report-link); + text-decoration: none; +} + +.report-footer a:hover { + text-decoration: underline; + opacity: 0.85; +} + +.report-footer__sep { + margin: 0 0.35em; + opacity: 0.55; +} diff --git a/packages/istanbul-report-html-modern/src/components/ReportFooter.tsx b/packages/istanbul-report-html-modern/src/components/ReportFooter.tsx new file mode 100644 index 0000000..2ab1639 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/ReportFooter.tsx @@ -0,0 +1,59 @@ +import "./ReportFooter.css"; + +function npmPackageVersionUrl(packageName: string, packageVersion: string): string { + return `https://www.npmjs.com/package/${packageName}/v/${encodeURIComponent(packageVersion)}`; +} + +function formatGeneratedAt(value: string | undefined): string { + const date = value ? new Date(value) : new Date(); + if (Number.isNaN(date.getTime())) { + return value ?? new Date().toISOString(); + } + return new Intl.DateTimeFormat(undefined, { + dateStyle: "medium", + timeStyle: "short", + }).format(date); +} + +export interface ReportFooterProps { + generatedAt?: string; + packageName?: string; + packageVersion?: string; +} + +export function ReportFooter({ generatedAt, packageName, packageVersion }: ReportFooterProps) { + const hasPackageInfo = Boolean(packageName && packageVersion); + + return ( + + ); +} diff --git a/packages/istanbul-report-html-modern/src/components/SummaryHeader.tsx b/packages/istanbul-report-html-modern/src/components/SummaryHeader.tsx new file mode 100644 index 0000000..8e7c019 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/SummaryHeader.tsx @@ -0,0 +1,120 @@ +import { Tag, Typography } from "antd"; +import type { FC } from "react"; + +import { getColor } from "../helpers/color"; +import type { DataSourceItem } from "../types"; + +const { Text } = Typography; + +const SUMMARY_LABELS: Record = { + statements: "Statements", + branches: "Branches", + functions: "Functions", + lines: "Lines", +}; + +const METRIC_ORDER = ["statements", "branches", "functions", "lines"] as const; + +const SummaryNav: FC<{ + reportName: string; + value: string; + onClick: (value: string) => void; +}> = ({ value, onClick, reportName }) => { + const crumbs = value === "" ? [reportName] : `${reportName}/${value}`.split("/"); + + return ( +
+ {crumbs.map((item, index) => { + const pathKey = `${reportName}-${index}-${item}`; + return ( +
+ { + onClick(value.split("/").slice(0, index).join("/")); + }} + > + {item} + + {index === value.split("/").length || !value ? null : /} +
+ ); + })} +
+ ); +}; + +const SummaryMetric: FC<{ data: DataSourceItem }> = ({ data }) => { + return ( +
+
+ {METRIC_ORDER.map((key) => { + const value = data[key]; + return ( +
+ {value.pct}% + + {SUMMARY_LABELS[key]}: + + + {value.covered}/{value.total} + +
+ ); + })} +
+
+ ); +}; + +const SummaryBar: FC<{ pct: number }> = ({ pct }) => { + return ( +
+ ); +}; + +const SummaryHeader: FC<{ + value: string; + onSelect: (value: string) => void; + data: DataSourceItem; + reportName: string; +}> = ({ value, onSelect, data, reportName }) => { + return ( +
+ + + +
+ ); +}; + +export default SummaryHeader; diff --git a/packages/istanbul-report-html-modern/src/components/SummaryList.tsx b/packages/istanbul-report-html-modern/src/components/SummaryList.tsx new file mode 100644 index 0000000..c1eed0d --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/SummaryList.tsx @@ -0,0 +1,95 @@ +import { ConfigProvider, Progress, Table } from "antd"; +import type { TableColumnsType } from "antd"; +import type { CSSProperties, FC } from "react"; +import Highlighter from "react-highlight-words"; + +import { getColor } from "../helpers/color"; +import type { DataSourceItem } from "../types"; + +const SummaryList: FC<{ + dataSource: DataSourceItem[]; + onSelect: (path: string) => void; + filenameKeywords: string; + style?: CSSProperties; +}> = ({ dataSource, onSelect, filenameKeywords, style }) => { + const columns: TableColumnsType = [ + { + title: "Files", + key: "path", + dataIndex: "path", + render(text: string) { + return ( + { + onSelect(text); + }} + > + + + ); + }, + }, + { + title: "Total", + key: "total", + dataIndex: ["statements", "total"], + sorter: (a, b) => a.statements.total - b.statements.total, + }, + { + title: "Covered", + key: "covered", + dataIndex: ["statements", "covered"], + sorter: (a, b) => a.statements.covered - b.statements.covered, + }, + { + title: "Coverage %", + width: "240px", + key: "c", + sorter: (a, b) => a.statements.pct - b.statements.pct, + dataIndex: ["statements", "pct"], + render(text: number) { + return ( + + ); + }, + }, + ]; + + return ( +
+ + + + + ); +}; + +export default SummaryList; diff --git a/packages/istanbul-report-html-modern/src/components/SummaryTree.tsx b/packages/istanbul-report-html-modern/src/components/SummaryTree.tsx new file mode 100644 index 0000000..48cb212 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/SummaryTree.tsx @@ -0,0 +1,100 @@ +import { FileOutlined, FolderFilled } from "@ant-design/icons"; +import { ConfigProvider, Progress, Table } from "antd"; +import type { TableColumnsType } from "antd"; +import type { CSSProperties, FC } from "react"; + +import { getColor } from "../helpers/color"; +import type { DataSourceItem } from "../types"; + +function isSourceFile(path: string): boolean { + return /\.(js|jsx|ts|tsx|mjs|cjs|mts|cts|vue|json|css|scss|less|html|md)$/i.test(path); +} + +const SummaryTree: FC<{ + dataSource: DataSourceItem[]; + onSelect: (path: string) => void; + style?: CSSProperties; +}> = ({ dataSource, onSelect, style }) => { + const columns: TableColumnsType = [ + { + title: "Files", + key: "path", + dataIndex: "path", + render(text: string) { + return ( + { + onSelect(text); + }} + > + {isSourceFile(text) ? ( + + ) : ( + + )} + {text.split("/").at(-1)} + + ); + }, + }, + { + title: "Total", + key: "total", + dataIndex: ["statements", "total"], + sorter: (a, b) => a.statements.total - b.statements.total, + }, + { + title: "Covered", + key: "covered", + dataIndex: ["statements", "covered"], + sorter: (a, b) => a.statements.covered - b.statements.covered, + }, + { + title: "Coverage %", + width: "240px", + key: "c", + dataIndex: ["statements", "pct"], + sorter: (a, b) => a.statements.pct - b.statements.pct, + render(text: number) { + return ( + + ); + }, + }, + ]; + + return ( +
+ +
+ + + ); +}; + +export default SummaryTree; diff --git a/packages/istanbul-report-html-modern/src/components/TopControl.tsx b/packages/istanbul-report-html-modern/src/components/TopControl.tsx new file mode 100644 index 0000000..3372bf9 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/TopControl.tsx @@ -0,0 +1,79 @@ +import Icon, { BarsOutlined, MoonOutlined, SunOutlined } from "@ant-design/icons"; +import { Button, Divider, Input, Segmented, Space, Tooltip } from "antd"; +import type { FC } from "react"; + +import TreeViewIcon from "../icons/TreeViewIcon"; +import { useTheme } from "../theme-context"; + +const TopControl: FC<{ + total: number; + showMode: string; + filenameKeywords: string; + onChangeShowMode: (mode: string) => void; + onChangeKeywords: (word: string) => void; +}> = ({ total, showMode, onChangeShowMode, onChangeKeywords, filenameKeywords }) => { + const { theme, toggleTheme } = useTheme(); + + return ( +
+
+
+ + { + onChangeShowMode(String(v)); + }} + options={[ + { + label: "Code Tree", + value: "tree", + icon: , + }, + { + label: "File List", + value: "list", + icon: , + }, + ]} + /> + + {total} {"Total Files"} + + +
+ + +
+ +
+ ); +}; + +export default TopControl; diff --git a/packages/istanbul-report-html-modern/src/css.d.ts b/packages/istanbul-report-html-modern/src/css.d.ts new file mode 100644 index 0000000..ef6d741 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/css.d.ts @@ -0,0 +1 @@ +declare module "*.css" {} diff --git a/packages/istanbul-report-html-modern/src/hash-route.ts b/packages/istanbul-report-html-modern/src/hash-route.ts new file mode 100644 index 0000000..548db45 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/hash-route.ts @@ -0,0 +1,48 @@ +const HASH_PREFIX = "#/"; + +function hashesEqual(left: string, right: string): boolean { + return normalizeHash(left) === normalizeHash(right); +} + +function normalizeHash(hash: string): string { + if (hash === "" || hash === "#" || hash === "#/") { + return "#/"; + } + return hash; +} + +/** `src/utils.ts` → `#/src/utils.ts` */ +export function pathToHash(path: string): string { + if (path === "") { + return HASH_PREFIX; + } + return `${HASH_PREFIX}${path.split("/").map(encodeURIComponent).join("/")}`; +} + +/** `#/src/utils.ts` → `src/utils.ts` */ +export function hashToPath(hash: string): string { + const raw = hash.startsWith("#") ? hash.slice(1) : hash; + const withoutQuery = (raw.split("?")[0] ?? "").replace(/^\/+/, "").replace(/\/+$/, ""); + if (withoutQuery === "") { + return ""; + } + return withoutQuery.split("/").map(decodeURIComponent).join("/"); +} + +export function readHashPath(): string { + if (typeof window === "undefined") { + return ""; + } + return hashToPath(window.location.hash); +} + +export function writeHashPath(path: string): void { + if (typeof window === "undefined") { + return; + } + const next = pathToHash(path); + if (hashesEqual(window.location.hash, next)) { + return; + } + window.location.hash = next; +} diff --git a/packages/istanbul-report-html-modern/src/helpers/annotate.ts b/packages/istanbul-report-html-modern/src/helpers/annotate.ts new file mode 100644 index 0000000..45e5866 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/helpers/annotate.ts @@ -0,0 +1,165 @@ +import type { FileCoverageData } from "../types"; + +export interface CoverageAnnotation { + startLine: number; + endLine: number; + startCol: number; + endCol: number; + type: "S" | "F" | "B" | "I" | "E"; +} + +export function annotateStatements( + fileCoverage: FileCoverageData, + structuredText: string, +): CoverageAnnotation[] { + const annotateStatementsList: CoverageAnnotation[] = []; + const statementStats = fileCoverage.s; + const statementMeta = fileCoverage.statementMap; + const rows = structuredText.split("\n"); + + for (const [stName, count] of Object.entries(statementStats)) { + const meta = statementMeta[stName]; + if (meta === undefined) { + continue; + } + if (count > 0) { + continue; + } + const startCol = meta.start.column; + const endCol = meta.end.column + 1; + const startLine = meta.start.line; + const endLine = meta.end.line; + const lineText = rows[startLine - 1] ?? ""; + const realEndCol = startCol > endCol ? lineText.length : endCol; + annotateStatementsList.push({ + startLine, + endLine, + startCol: startCol + 1, + endCol: realEndCol + 1, + type: "S", + }); + } + return annotateStatementsList; +} + +export function annotateFunctions( + fileCoverage: FileCoverageData, + structuredText: string, +): CoverageAnnotation[] { + const fnStats = fileCoverage.f; + const fnMeta = fileCoverage.fnMap; + if (fnStats === undefined) { + return []; + } + const rows = structuredText.split("\n"); + const list: CoverageAnnotation[] = []; + + for (const [fName, count] of Object.entries(fnStats)) { + const meta = fnMeta[fName]; + if (meta === undefined || count > 0) { + continue; + } + const decl = meta.decl ?? meta.loc; + const startCol = decl.start.column; + let endCol = decl.end.column + 1; + const startLine = decl.start.line; + const endLine = decl.end.line; + if (endLine !== startLine) { + endCol = (rows[startLine - 1] ?? "").length; + } + list.push({ + startLine, + endLine, + startCol: startCol + 1, + endCol: endCol + 1, + type: "F", + }); + } + return list; +} + +export function annotateBranches( + fileCoverage: FileCoverageData, + structuredText: string, +): CoverageAnnotation[] { + const branchStats = fileCoverage.b; + const branchMeta = fileCoverage.branchMap; + if (branchStats === undefined) { + return []; + } + + const rows = structuredText.split("\n"); + const arr: CoverageAnnotation[] = []; + + for (const [branchName, branchArray] of Object.entries(branchStats)) { + const meta = branchMeta[branchName]; + if (meta === undefined) { + continue; + } + const sumCount = branchArray.reduce((p, n) => p + n, 0); + const metaArray = [...meta.locations]; + + if (!(sumCount > 0 || (sumCount === 0 && branchArray.length === 1))) { + continue; + } + + if ( + meta.type === "if" && + branchArray.length === 2 && + metaArray.length === 1 && + branchArray[1] === 0 + ) { + metaArray[1] = { + start: { line: 0, column: 0 }, + end: { line: 0, column: 0 }, + }; + } + + for (let i = 0; i < branchArray.length && i < metaArray.length; i += 1) { + const count = branchArray[i]; + let loc = metaArray[i]; + if (count === undefined || loc === undefined) { + continue; + } + + if (count === 0 && loc.start.line === 0 && meta.type === "if" && i > 0) { + const prevMeta = metaArray[i - 1]; + if (prevMeta !== undefined) { + loc = prevMeta; + } + } + + if (count !== 0) { + continue; + } + + const startLine = loc.start.line; + if (rows[startLine - 1] === undefined) { + continue; + } + + const startCol = loc.start.column; + const endCol = loc.end.column + 1; + const endLine = loc.end.line; + + if (meta.type === "if") { + arr.push({ + startLine, + endLine, + startCol: startCol + 1, + endCol: endCol + 1, + type: i === 0 ? "I" : "E", + }); + } else { + arr.push({ + startLine, + endLine, + startCol: startCol + 1, + endCol: endCol + 1, + type: "B", + }); + } + } + } + return arr; +} diff --git a/packages/istanbul-report-html-modern/src/helpers/build-report-files.ts b/packages/istanbul-report-html-modern/src/helpers/build-report-files.ts new file mode 100644 index 0000000..4cc0c73 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/helpers/build-report-files.ts @@ -0,0 +1,33 @@ +import { projectRootBaseName, resolveSource, toRelativePath } from "../paths"; +import type { FileCoverageData, ReportAppFile } from "../types"; + +export interface ReportDataLike { + projectRoot?: string; + coverage: Record; + sources: Record; +} + +/** Build `ReportApp` files from serialized report payload (absolute paths + sources). */ +export function buildReportFiles(reportData: ReportDataLike): { + files: ReportAppFile[]; + projectRoot: string; + name: string; +} { + const projectRoot = reportData.projectRoot ?? ""; + const coverage = reportData.coverage as Record; + + const files = Object.entries(coverage).map(([key, data]) => { + const absPath = data.path || key; + return { + ...data, + path: absPath, + source: resolveSource(reportData.sources, toRelativePath(absPath, projectRoot), projectRoot), + }; + }); + + return { + files, + projectRoot, + name: projectRootBaseName(projectRoot), + }; +} diff --git a/packages/istanbul-report-html-modern/src/helpers/color.ts b/packages/istanbul-report-html-modern/src/helpers/color.ts new file mode 100644 index 0000000..6c244d1 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/helpers/color.ts @@ -0,0 +1,9 @@ +export function getColor(pct: number): string { + if (pct >= 80) { + return "rgb(33,181,119)"; + } + if (pct >= 50) { + return "rgb(244,176,27)"; + } + return "rgb(245,32,32)"; +} diff --git a/packages/istanbul-report-html-modern/src/helpers/decompress.ts b/packages/istanbul-report-html-modern/src/helpers/decompress.ts new file mode 100644 index 0000000..f9872cf --- /dev/null +++ b/packages/istanbul-report-html-modern/src/helpers/decompress.ts @@ -0,0 +1,40 @@ +export function base64ToUint8Array(base64: string) { + const binaryString = atob(base64); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; +} + +export async function decompressGzip(compressedData: Uint8Array) { + const stream = new DecompressionStream("gzip"); + const writer = stream.writable.getWriter(); + const reader = stream.readable.getReader(); + + writer.write(compressedData as BufferSource); + writer.close(); + + const chunks = []; + let done = false; + + while (!done) { + const { value, done: readerDone } = await reader.read(); + done = readerDone; + if (value) { + chunks.push(value); + } + } + + // Merge decompressed chunks into one buffer. + const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const result = new Uint8Array(totalLength); + let offset = 0; + + for (const chunk of chunks) { + result.set(chunk, offset); + offset += chunk.length; + } + + return new TextDecoder().decode(result); +} diff --git a/packages/istanbul-report-html-modern/src/helpers/derive-views.ts b/packages/istanbul-report-html-modern/src/helpers/derive-views.ts new file mode 100644 index 0000000..92d01fc --- /dev/null +++ b/packages/istanbul-report-html-modern/src/helpers/derive-views.ts @@ -0,0 +1,44 @@ +import type { DataSourceItem } from "../types"; +import { buildSummaryTree } from "./summary"; + +function matchesKeywords(item: { path: string }, keywords: string): boolean { + if (keywords === "") { + return true; + } + return item.path.toLowerCase().includes(keywords.toLowerCase()); +} + +function underPath(item: { path: string }, startValue: string): boolean { + if (startValue === "") { + return true; + } + return item.path === startValue || item.path.startsWith(`${startValue}/`); +} + +/** Filter and group coverage rows for tree / list / header views. */ +export function deriveSummaryViews({ + dataSource, + filenameKeywords, + value, +}: { + dataSource: DataSourceItem[]; + filenameKeywords: string; + value: string; +}): { + treeDataSource: DataSourceItem[]; + rootDataSource: DataSourceItem; + listDataSource: DataSourceItem[]; +} { + const listDataSource = dataSource.filter( + (item) => underPath(item, value) && matchesKeywords(item, filenameKeywords), + ); + const tree = buildSummaryTree(value, listDataSource); + return { + treeDataSource: tree.children, + rootDataSource: { + path: tree.path, + ...tree.summary, + }, + listDataSource, + }; +} diff --git a/packages/istanbul-report-html-modern/src/helpers/empty-coverage.ts b/packages/istanbul-report-html-modern/src/helpers/empty-coverage.ts new file mode 100644 index 0000000..020433b --- /dev/null +++ b/packages/istanbul-report-html-modern/src/helpers/empty-coverage.ts @@ -0,0 +1,11 @@ +import type { FileCoverageData } from "../types"; + +export const emptyFileCoverage: FileCoverageData = { + path: "", + statementMap: {}, + fnMap: {}, + branchMap: {}, + s: {}, + f: {}, + b: {}, +}; diff --git a/packages/istanbul-report-html-modern/src/helpers/line-hits.ts b/packages/istanbul-report-html-modern/src/helpers/line-hits.ts new file mode 100644 index 0000000..4767b75 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/helpers/line-hits.ts @@ -0,0 +1,63 @@ +import type { FileCoverageData } from "../types"; + +export interface LineHit { + executionNumber: number; +} + +/** Derive per-line hit counts from statement coverage for the editor gutter. */ +export function computeLineHits( + fileCoverage: FileCoverageData, + fileDetail: string, +): { + times: { lineNumber: number; count: number }[]; + rows: string[]; + maxWidth: number; + lines: LineHit[]; +} { + const empty = { + times: [] as { lineNumber: number; count: number }[], + rows: [] as string[], + maxWidth: 0, + lines: [] as LineHit[], + }; + + if (fileCoverage.s === undefined) { + return empty; + } + + const rows = fileDetail.split("\n"); + const maxWidth = rows.reduce((max, row) => Math.max(max, row.length), 0); + + const lineMap: Record = Object.create(null) as Record; + for (const [st, count] of Object.entries(fileCoverage.s)) { + const meta = fileCoverage.statementMap[st]; + if (meta === undefined) { + continue; + } + const { line } = meta.start; + const prevVal = lineMap[line]; + if (prevVal === undefined || prevVal < count) { + lineMap[line] = count; + } + } + + const times = Object.entries(lineMap).map(([lineNumber, count]) => ({ + lineNumber: Number(lineNumber), + count, + })); + + const lines: LineHit[] = []; + for (let i = 0; i < rows.length; i++) { + const found = times.find((n) => n.lineNumber === i + 1); + lines.push({ + executionNumber: found === undefined ? -1 : found.count, + }); + } + + return { + times, + rows, + lines, + maxWidth, + }; +} diff --git a/packages/istanbul-report-html-modern/src/helpers/loadData.ts b/packages/istanbul-report-html-modern/src/helpers/loadData.ts new file mode 100644 index 0000000..4d5e784 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/helpers/loadData.ts @@ -0,0 +1,17 @@ +import type { ReportData } from "../report-data.ts"; +import { base64ToUint8Array, decompressGzip } from "./decompress.ts"; + +/** + * Decode gzip+base64 `window.reportData` (as embedded by the html-modern reporter) + * into a plain object. No-op when data is already an object (e.g. local Vite dev). + */ +export async function loadReportData(): Promise { + const embeddedReportData = window.reportData as ReportData | string | undefined; + + if (typeof embeddedReportData !== "string") { + return; + } + + const decompressedText = await decompressGzip(base64ToUint8Array(embeddedReportData)); + window.reportData = JSON.parse(decompressedText) as ReportData; +} diff --git a/packages/istanbul-report-html-modern/src/helpers/summary.ts b/packages/istanbul-report-html-modern/src/helpers/summary.ts new file mode 100644 index 0000000..5970a26 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/helpers/summary.ts @@ -0,0 +1,126 @@ +import type { CoverageTotals, DataSourceItem, FileCoverageData } from "../types"; + +export function percent(covered: number, total: number): number { + if (total === 0) { + return 100; + } + return Math.round((covered / total) * 10000) / 100; +} + +export function emptyTotals(): CoverageTotals { + return { total: 0, covered: 0, skipped: 0, pct: 100 }; +} + +export function mergeTotals(a: CoverageTotals, b: CoverageTotals): CoverageTotals { + const total = a.total + b.total; + const covered = a.covered + b.covered; + const skipped = a.skipped + b.skipped; + return { total, covered, skipped, pct: percent(covered, total) }; +} + +export function emptySummary(): Omit { + return { + statements: emptyTotals(), + branches: emptyTotals(), + functions: emptyTotals(), + lines: emptyTotals(), + }; +} + +function hitsToTotals(hits: readonly number[]): CoverageTotals { + const total = hits.length; + const covered = hits.filter((count) => count > 0).length; + return { total, covered, skipped: 0, pct: percent(covered, total) }; +} + +export function fileCoverageToSummary(coverage: FileCoverageData): Omit { + const lineHits = new Map(); + for (const [id, range] of Object.entries(coverage.statementMap)) { + const hits = coverage.s[id] ?? 0; + const line = range.start.line; + const prev = lineHits.get(line); + if (prev === undefined || hits > prev) { + lineHits.set(line, hits); + } + } + + return { + statements: hitsToTotals(Object.values(coverage.s)), + functions: hitsToTotals(Object.values(coverage.f)), + branches: hitsToTotals(Object.values(coverage.b).flat()), + lines: hitsToTotals([...lineHits.values()]), + }; +} + +export function mergeSummaries( + items: ReadonlyArray>, +): Omit { + return items.reduce>( + (acc, item) => ({ + statements: mergeTotals(acc.statements, item.statements), + branches: mergeTotals(acc.branches, item.branches), + functions: mergeTotals(acc.functions, item.functions), + lines: mergeTotals(acc.lines, item.lines), + }), + emptySummary(), + ); +} + +export function filesToDataSource(files: readonly FileCoverageData[]): DataSourceItem[] { + return files.map((file) => ({ + path: file.path, + ...fileCoverageToSummary(file), + })); +} + +export function buildSummaryTree( + value: string, + files: readonly DataSourceItem[], +): { + path: string; + summary: Omit; + children: DataSourceItem[]; +} { + const prefix = value === "" ? "" : `${value}/`; + const under = files.filter((item) => { + if (value === "") { + return true; + } + return item.path === value || item.path.startsWith(prefix); + }); + + const groups = new Map(); + for (const file of under) { + if (file.path === value) { + continue; + } + const rest = value === "" ? file.path : file.path.slice(prefix.length); + const segment = rest.split("/")[0]; + if (segment === undefined || segment === "") { + continue; + } + const childPath = value === "" ? segment : `${value}/${segment}`; + const list = groups.get(childPath); + if (list === undefined) { + groups.set(childPath, [file]); + } else { + list.push(file); + } + } + + const children = [...groups.entries()] + .map(([path, group]) => { + const only = group.length === 1 ? group[0] : undefined; + if (only !== undefined && only.path === path) { + return only; + } + return { path, ...mergeSummaries(group) }; + }) + .sort((a, b) => a.path.localeCompare(b.path)); + + return { + path: value, + summary: mergeSummaries(under), + children, + }; +} diff --git a/packages/istanbul-report-html-modern/src/icons/TreeViewIcon.tsx b/packages/istanbul-report-html-modern/src/icons/TreeViewIcon.tsx new file mode 100644 index 0000000..78a7988 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/icons/TreeViewIcon.tsx @@ -0,0 +1,18 @@ +import type { SVGProps } from "react"; + +export default function TreeViewIcon(props: SVGProps) { + return ( + + + + ); +} diff --git a/packages/istanbul-report-html-modern/src/index.css b/packages/istanbul-report-html-modern/src/index.css new file mode 100644 index 0000000..57ac1b1 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/index.css @@ -0,0 +1,147 @@ +:root, +[data-theme="light"] { + color-scheme: light; + --report-bg: #ffffff; + --report-text: rgba(0, 0, 0, 0.88); + --report-link: #0071c2; + --report-line-coverage-bg: #eaeaea; + --report-line-coverage-text: rgba(0, 0, 0, 0.5); + --report-line-hit-covered: rgb(230, 245, 208); + --report-line-hit-uncovered: #f3aeac; + --report-line-hit-neutral: rgb(234, 234, 234); + --report-highlight-bg: rgb(244, 176, 27); + --report-highlight-text: #ffffff; + --report-uncovered-branch-bg: rgba(255, 229, 100, 0.6); + --report-uncovered-bg: #f3aeac; + --report-decoration-bg: #000000; + --report-decoration-text: #ffff00; +} + +[data-theme="dark"] { + color-scheme: dark; + --report-bg: #1e1e1e; + --report-text: rgba(255, 255, 255, 0.85); + --report-link: #0071c2; + --report-line-coverage-bg: #303030; + --report-line-coverage-text: rgba(255, 255, 255, 0.45); + --report-line-hit-covered: rgb(45, 74, 34); + --report-line-hit-uncovered: #5c3534; + --report-line-hit-neutral: #303030; + --report-highlight-bg: rgb(180, 130, 20); + --report-highlight-text: #ffffff; + --report-uncovered-branch-bg: rgba(180, 140, 0, 0.35); + --report-uncovered-bg: #5c3534; + --report-decoration-bg: #434343; + --report-decoration-text: #fadb14; +} + +html, +body, +#root { + margin: 0; + height: 100%; + background: var(--report-bg); + color: var(--report-text); +} + +#app { + height: 100%; +} + +body { + font-family: + system-ui, + -apple-system, + "Segoe UI", + Roboto, + sans-serif; +} + +.istanbul-html-modern .coverage-detail-spin-wrapper { + height: 100%; +} +.istanbul-html-modern .coverage-detail-spin-wrapper > .ant-spin-container { + height: 100%; +} +.istanbul-html-modern { + height: 100%; + display: flex; + flex-direction: column; + padding: 6px; + box-sizing: border-box; + background: var(--report-bg); +} + +.istanbul-html-modern .report-scroll-body { + flex: 1; + min-height: 0; + overflow: auto; +} +.istanbul-html-modern a { + color: var(--report-link); + cursor: pointer; +} +.istanbul-html-modern a:hover { + color: var(--report-link); + opacity: 0.8; + text-decoration: underline; +} +.istanbul-html-modern .line-number { + margin-right: 5px; +} +.istanbul-html-modern .line-number-wrapper { + display: flex; + justify-self: end; +} +.istanbul-html-modern .line-number-wrapper > span { + display: block; +} + +.istanbul-html-modern .line-coverage { + background-color: var(--report-line-coverage-bg); + width: 10px; + padding-right: 5px; + color: var(--report-line-coverage-text); +} + +.istanbul-html-modern .YourHighlightClass { + padding: 0; + color: var(--report-highlight-text); + background: var(--report-highlight-bg); +} + +.istanbul-html-modern .content-class-no-found-branch { + background: var(--report-uncovered-branch-bg); +} + +.istanbul-html-modern .content-class-no-found { + background: var(--report-uncovered-bg); +} + +.istanbul-html-modern .insert-e-decoration::before { + content: "E"; + background-color: var(--report-decoration-bg); + color: var(--report-decoration-text); + padding: 0 2px; + border-radius: 2px; + margin-right: 5px; +} + +.istanbul-html-modern .insert-i-decoration::before { + content: "I"; + background-color: var(--report-decoration-bg); + color: var(--report-decoration-text); + padding: 0 2px; + border-radius: 2px; + margin-right: 5px; +} + +.istanbul-html-modern .coverage-detail-container { + height: 100%; + position: relative; +} + +.istanbul-html-modern .coverage-detail-editor { + width: 100%; + height: 100%; +} diff --git a/packages/istanbul-report-html-modern/src/index.ts b/packages/istanbul-report-html-modern/src/index.ts new file mode 100644 index 0000000..ec9de9f --- /dev/null +++ b/packages/istanbul-report-html-modern/src/index.ts @@ -0,0 +1,15 @@ +import "./index.css"; + +export { Report } from "./Report"; +export { ReportApp } from "./ReportApp"; +export { buildReportFiles } from "./helpers/build-report-files"; +export { filesToDataSource, buildSummaryTree } from "./helpers/summary"; +export { projectRootBaseName, resolveSource, toAbsolutePath, toRelativePath } from "./paths"; +export type { + DataSourceItem, + FileCoverageData, + FileDataResponse, + ReportAppFile, + ReportAppProps, + ReportProps, +} from "./types"; diff --git a/packages/istanbul-report-html-modern/src/main.tsx b/packages/istanbul-report-html-modern/src/main.tsx new file mode 100644 index 0000000..2369613 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/main.tsx @@ -0,0 +1,14 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import "monaco-editor-css"; + +import { loadReportData } from "./helpers/loadData.ts"; +import ReportPage from "./ReportPage.tsx"; + +await loadReportData(); + +createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/packages/istanbul-report-html-modern/src/monaco-esm.d.ts b/packages/istanbul-report-html-modern/src/monaco-esm.d.ts new file mode 100644 index 0000000..42af2b9 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/monaco-esm.d.ts @@ -0,0 +1,9 @@ +declare module "monaco-editor/editor/editor.api" { + export * from "monaco-editor"; +} + +declare module "monaco-editor/editor/contrib/hover/browser/hoverContribution" {} +declare module "monaco-editor/languages/definitions/javascript/register" {} +declare module "monaco-editor/languages/definitions/typescript/register" {} +declare module "monaco-editor/languages/definitions/css/register" {} +declare module "monaco-editor/languages/definitions/html/register" {} diff --git a/packages/istanbul-report-html-modern/src/monaco.ts b/packages/istanbul-report-html-modern/src/monaco.ts new file mode 100644 index 0000000..353af19 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/monaco.ts @@ -0,0 +1,32 @@ +import * as monaco from "monaco-editor/editor/editor.api"; +import "monaco-editor/editor/contrib/hover/browser/hoverContribution"; +import "monaco-editor/languages/definitions/css/register"; +import "monaco-editor/languages/definitions/html/register"; +import "monaco-editor/languages/definitions/javascript/register"; +import "monaco-editor/languages/definitions/typescript/register"; + +export { monaco }; + +export function languageFromPath(filePath: string): string { + const ext = filePath.split(".").pop()?.toLowerCase(); + switch (ext) { + case "ts": + case "mts": + case "cts": + case "tsx": + return "typescript"; + case "js": + case "mjs": + case "cjs": + case "jsx": + return "javascript"; + case "css": + case "scss": + return "css"; + case "html": + case "htm": + return "html"; + default: + return "plaintext"; + } +} diff --git a/packages/istanbul-report-html-modern/src/paths.ts b/packages/istanbul-report-html-modern/src/paths.ts new file mode 100644 index 0000000..402bf35 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/paths.ts @@ -0,0 +1,81 @@ +/** POSIX-ify slashes; keep a leading `/` so absolute Unix paths stay absolute. */ +export function posixify(filePath: string): string { + return filePath.replaceAll("\\", "/"); +} + +/** Trim trailing slashes but keep filesystem root as `/`. */ +function normalizeRoot(projectRoot: string): string { + const posix = posixify(projectRoot); + if (posix === "/" || /^[/\\]+$/.test(posix)) { + return "/"; + } + return posix.replace(/\/+$/, ""); +} + +/** Strip `projectRoot/` prefix so UI paths are project-relative. */ +export function toRelativePath(filePath: string, projectRoot: string): string { + const abs = posixify(filePath); + const root = normalizeRoot(projectRoot); + if (!root) { + return abs; + } + if (abs === root) { + return ""; + } + if (root === "/") { + return abs.startsWith("/") ? abs.slice(1) : abs; + } + const prefix = `${root}/`; + return abs.startsWith(prefix) ? abs.slice(prefix.length) : abs; +} + +export function toAbsolutePath(relPath: string, projectRoot: string): string { + const rel = posixify(relPath).replace(/^\//, ""); + const root = normalizeRoot(projectRoot); + if (!root) { + return rel; + } + if (!rel) { + return root; + } + if (root === "/") { + return `/${rel}`; + } + return `${root}/${rel}`; +} + +/** Last path segment of `projectRoot`, used as the report display name. */ +export function projectRootBaseName(projectRoot: string): string { + const root = normalizeRoot(projectRoot); + if (!root || root === "/") { + return "Coverage"; + } + const parts = root.split("/").filter(Boolean); + return parts[parts.length - 1] ?? "Coverage"; +} + +export function resolveSource( + sources: Record, + relPath: string, + projectRoot: string, +): string { + const abs = toAbsolutePath(relPath, projectRoot); + const direct = sources[abs] ?? sources[relPath]; + if (direct !== undefined) { + return direct; + } + + const needle = posixify(relPath); + if (!needle) { + return ""; + } + + for (const [key, src] of Object.entries(sources)) { + const normalized = posixify(key); + if (normalized === posixify(abs) || normalized.endsWith(`/${needle}`)) { + return src; + } + } + + return ""; +} diff --git a/packages/istanbul-report-html-modern/src/report-data.ts b/packages/istanbul-report-html-modern/src/report-data.ts new file mode 100644 index 0000000..3ab13a2 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/report-data.ts @@ -0,0 +1,38 @@ +/** Keep in sync with packages/istanbul-lib-report/src/reports/html-modern/coverage-report.ts */ +export interface ReportData { + html: { + verbose?: boolean; + subdir?: string; + skipEmpty?: boolean; + metricsToShow?: ("lines" | "branches" | "functions" | "statements")[]; + }; + istanbul: { + dir: string; + watermarks: { + statements: [number, number]; + functions: [number, number]; + branches: [number, number]; + lines: [number, number]; + }; + defaultSummarizer: "flat" | "nested" | "pkg" | "defaultSummarizer"; + summarizer?: "flat" | "nested" | "pkg" | "defaultSummarizer"; + sourceFinder: "filesystem" | "custom"; + }; + stats: { + coverageFileCount: number; + sourceFileCount: number; + }; + /** project root used to relativize coverage paths in the UI (inferred or explicit) */ + projectRoot: string; + coverage: Record; + sources: Record; + generatedAt?: string; + packageName?: string; + packageVersion?: string; +} + +declare global { + interface Window { + reportData?: ReportData; + } +} diff --git a/packages/istanbul-report-html-modern/src/theme-context.tsx b/packages/istanbul-report-html-modern/src/theme-context.tsx new file mode 100644 index 0000000..7f2caf1 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/theme-context.tsx @@ -0,0 +1,91 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useState, + type FC, + type ReactNode, +} from "react"; + +export type ThemeMode = "light" | "dark"; + +const STORAGE_KEY = "istanbul-html-modern-theme"; + +function getSystemTheme(): ThemeMode { + if (typeof window === "undefined") { + return "light"; + } + return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; +} + +function readStoredTheme(): ThemeMode | null { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored === "light" || stored === "dark") { + return stored; + } + } catch { + // ignore storage errors + } + return null; +} + +export function resolveTheme(): ThemeMode { + return readStoredTheme() ?? getSystemTheme(); +} + +function applyTheme(theme: ThemeMode): void { + document.documentElement.dataset.theme = theme; + document.documentElement.style.colorScheme = theme; +} + +interface ThemeContextValue { + theme: ThemeMode; + toggleTheme: () => void; +} + +const ThemeContext = createContext(null); + +export const ThemeProvider: FC<{ children: ReactNode }> = ({ children }) => { + const [theme, setTheme] = useState(() => resolveTheme()); + + useEffect(() => { + applyTheme(theme); + }, [theme]); + + useEffect(() => { + const media = window.matchMedia("(prefers-color-scheme: dark)"); + const onChange = () => { + if (readStoredTheme() === null) { + setTheme(getSystemTheme()); + } + }; + media.addEventListener("change", onChange); + return () => { + media.removeEventListener("change", onChange); + }; + }, []); + + const toggleTheme = useCallback(() => { + setTheme((prev) => { + const next: ThemeMode = prev === "light" ? "dark" : "light"; + try { + localStorage.setItem(STORAGE_KEY, next); + } catch { + // ignore storage errors + } + return next; + }); + }, []); + + return {children}; +}; + +export function useTheme(): ThemeContextValue { + const context = useContext(ThemeContext); + if (context === null) { + throw new Error("useTheme must be used within ThemeProvider"); + } + return context; +} diff --git a/packages/istanbul-report-html-modern/src/types.ts b/packages/istanbul-report-html-modern/src/types.ts new file mode 100644 index 0000000..c0eae6c --- /dev/null +++ b/packages/istanbul-report-html-modern/src/types.ts @@ -0,0 +1,74 @@ +export interface CoverageLocation { + line: number; + column: number; +} + +export interface CoverageRange { + start: CoverageLocation; + end: CoverageLocation; +} + +export interface FunctionMapping { + name?: string; + decl?: CoverageRange; + loc: CoverageRange; +} + +export interface BranchMapping { + loc?: CoverageRange; + type: string; + locations: CoverageRange[]; +} + +/** Shape compatible with istanbul-lib-coverage `FileCoverageData`. */ +export interface FileCoverageData { + path: string; + statementMap: Record; + fnMap: Record; + branchMap: Record; + s: Record; + f: Record; + b: Record; +} + +export interface CoverageTotals { + total: number; + covered: number; + skipped: number; + pct: number; +} + +export interface DataSourceItem { + path: string; + statements: CoverageTotals; + branches: CoverageTotals; + functions: CoverageTotals; + lines: CoverageTotals; +} + +export interface FileDataResponse { + fileCoverage: FileCoverageData; + fileContent: string; +} + +export interface ReportProps { + /** Report display name */ + name: string; + /** Currently selected path */ + value: string; + dataSource: DataSourceItem[]; + onSelect: (val: string) => Promise; +} + +export interface ReportAppFile extends FileCoverageData { + source: string; +} + +export interface ReportAppProps { + files: ReportAppFile[]; + /** project root used to relativize coverage paths in the UI (inferred or explicit) */ + projectRoot: string; + name?: string; + /** Initial path when there is no hash, e.g. `src/index.ts` */ + defaultValue?: string; +} diff --git a/packages/istanbul-report-html-modern/src/useHashPath.ts b/packages/istanbul-report-html-modern/src/useHashPath.ts new file mode 100644 index 0000000..27b938e --- /dev/null +++ b/packages/istanbul-report-html-modern/src/useHashPath.ts @@ -0,0 +1,30 @@ +import { useCallback, useEffect, useState } from "react"; + +import { readHashPath, writeHashPath } from "./hash-route"; + +export function useHashPath(defaultPath = ""): [string, (path: string) => void] { + const [path, setPath] = useState(() => { + const fromHash = readHashPath(); + return fromHash === "" ? defaultPath : fromHash; + }); + + useEffect(() => { + const onHashChange = () => { + setPath(readHashPath()); + }; + window.addEventListener("hashchange", onHashChange); + return () => { + window.removeEventListener("hashchange", onHashChange); + }; + }, []); + + useEffect(() => { + writeHashPath(path); + }, [path]); + + const navigate = useCallback((next: string) => { + setPath(next); + }, []); + + return [path, navigate]; +} diff --git a/packages/istanbul-report-html-modern/src/vite-env.d.ts b/packages/istanbul-report-html-modern/src/vite-env.d.ts new file mode 100644 index 0000000..863901a --- /dev/null +++ b/packages/istanbul-report-html-modern/src/vite-env.d.ts @@ -0,0 +1,3 @@ +/// + +declare module "monaco-editor-css" {} diff --git a/packages/istanbul-report-html-modern/test/paths.test.ts b/packages/istanbul-report-html-modern/test/paths.test.ts new file mode 100644 index 0000000..2175e50 --- /dev/null +++ b/packages/istanbul-report-html-modern/test/paths.test.ts @@ -0,0 +1,69 @@ +import { assert, describe, it } from "vitest"; + +import { projectRootBaseName, resolveSource, toAbsolutePath, toRelativePath } from "../src/paths"; + +describe("toRelativePath", () => { + it("strips the projectRoot prefix", () => { + assert.equal(toRelativePath("/proj/src/a.ts", "/proj"), "src/a.ts"); + }); + + it("returns empty string when path equals projectRoot", () => { + assert.equal(toRelativePath("/proj", "/proj"), ""); + }); + + it("returns the original path when prefix does not match", () => { + assert.equal(toRelativePath("/other/a.ts", "/proj"), "/other/a.ts"); + }); + + it("normalizes Windows separators", () => { + assert.equal(toRelativePath("C:\\proj\\src\\a.ts", "C:\\proj"), "src/a.ts"); + }); + + it("returns the absolute path when projectRoot is empty", () => { + assert.equal(toRelativePath("/proj/a.ts", ""), "/proj/a.ts"); + }); +}); + +describe("toAbsolutePath", () => { + it("joins projectRoot and a relative path", () => { + assert.equal(toAbsolutePath("src/a.ts", "/proj"), "/proj/src/a.ts"); + }); + + it("returns projectRoot when relative path is empty", () => { + assert.equal(toAbsolutePath("", "/proj"), "/proj"); + }); + + it("handles filesystem root as projectRoot", () => { + assert.equal(toAbsolutePath("a.ts", "/"), "/a.ts"); + }); +}); + +describe("projectRootBaseName", () => { + it("returns the last path segment", () => { + assert.equal(projectRootBaseName("/Users/me/istanbuljs"), "istanbuljs"); + }); + + it("returns Coverage for empty or root paths", () => { + assert.equal(projectRootBaseName(""), "Coverage"); + assert.equal(projectRootBaseName("/"), "Coverage"); + }); +}); + +describe("resolveSource", () => { + const sources = { + "/proj/src/a.ts": "consola", + "/proj/src/b.ts": "export {}", + }; + + it("resolves by absolute path", () => { + assert.equal(resolveSource(sources, "src/a.ts", "/proj"), "consola"); + }); + + it("falls back to suffix matching", () => { + assert.equal(resolveSource({ "/abs/deep/src/c.ts": "c" }, "src/c.ts", "/other"), "c"); + }); + + it("returns empty string when not found", () => { + assert.equal(resolveSource(sources, "missing.ts", "/proj"), ""); + }); +}); diff --git a/packages/istanbul-report-html-modern/test/summary.test.ts b/packages/istanbul-report-html-modern/test/summary.test.ts new file mode 100644 index 0000000..3ed948b --- /dev/null +++ b/packages/istanbul-report-html-modern/test/summary.test.ts @@ -0,0 +1,117 @@ +import { assert, describe, it } from "vitest"; + +import { buildReportFiles } from "../src/helpers/build-report-files"; +import { deriveSummaryViews } from "../src/helpers/derive-views"; +import { computeLineHits } from "../src/helpers/line-hits"; +import { + buildSummaryTree, + fileCoverageToSummary, + filesToDataSource, + percent, +} from "../src/helpers/summary"; +import type { FileCoverageData } from "../src/types"; + +function makeFile(path: string, statementHits: number[]): FileCoverageData { + const statementMap: FileCoverageData["statementMap"] = {}; + const s: Record = {}; + statementHits.forEach((hits, index) => { + const id = String(index); + statementMap[id] = { + start: { line: index + 1, column: 0 }, + end: { line: index + 1, column: 1 }, + }; + s[id] = hits; + }); + return { + path, + statementMap, + fnMap: {}, + branchMap: {}, + s, + f: {}, + b: {}, + }; +} + +describe("percent / filesToDataSource", () => { + it("computes rounded coverage percent", () => { + assert.equal(percent(1, 2), 50); + assert.equal(percent(0, 0), 100); + }); + + it("maps files to summary rows", () => { + const rows = filesToDataSource([makeFile("src/a.ts", [1, 0])]); + assert.equal(rows.length, 1); + assert.equal(rows[0]!.path, "src/a.ts"); + assert.equal(rows[0]!.statements.total, 2); + assert.equal(rows[0]!.statements.covered, 1); + assert.equal(rows[0]!.lines.total, 2); + }); +}); + +describe("buildSummaryTree", () => { + it("groups children under the current path", () => { + const tree = buildSummaryTree("", [ + { path: "src/a.ts", ...fileCoverageToSummary(makeFile("src/a.ts", [1])) }, + { path: "src/b.ts", ...fileCoverageToSummary(makeFile("src/b.ts", [0])) }, + { path: "lib/c.ts", ...fileCoverageToSummary(makeFile("lib/c.ts", [1])) }, + ]); + + assert.equal(tree.path, ""); + assert.deepEqual( + tree.children.map((child) => child.path), + ["lib", "src"], + ); + }); +}); + +describe("deriveSummaryViews", () => { + it("filters by keywords and current path", () => { + const dataSource = [ + { path: "src/a.ts", ...fileCoverageToSummary(makeFile("src/a.ts", [1])) }, + { path: "src/util/b.ts", ...fileCoverageToSummary(makeFile("src/util/b.ts", [1])) }, + { path: "lib/c.ts", ...fileCoverageToSummary(makeFile("lib/c.ts", [1])) }, + ]; + + const views = deriveSummaryViews({ + dataSource, + filenameKeywords: "util", + value: "src", + }); + + assert.deepEqual( + views.listDataSource.map((item) => item.path), + ["src/util/b.ts"], + ); + }); +}); + +describe("computeLineHits", () => { + it("maps statement hits onto source lines", () => { + const coverage = makeFile("a.ts", [3, 0]); + const result = computeLineHits(coverage, "line1\nline2\nline3"); + assert.equal(result.lines[0]!.executionNumber, 3); + assert.equal(result.lines[1]!.executionNumber, 0); + assert.equal(result.lines[2]!.executionNumber, -1); + }); +}); + +describe("buildReportFiles", () => { + it("attaches sources and keeps absolute coverage paths", () => { + const { files, projectRoot, name } = buildReportFiles({ + projectRoot: "/proj", + coverage: { + "/proj/src/a.ts": makeFile("/proj/src/a.ts", [1]), + }, + sources: { + "/proj/src/a.ts": "export {}", + }, + }); + + assert.equal(projectRoot, "/proj"); + assert.equal(name, "proj"); + assert.equal(files.length, 1); + assert.equal(files[0]!.path, "/proj/src/a.ts"); + assert.equal(files[0]!.source, "export {}"); + }); +}); diff --git a/packages/istanbul-report-html-modern/tsconfig.json b/packages/istanbul-report-html-modern/tsconfig.json new file mode 100644 index 0000000..b4ea77c --- /dev/null +++ b/packages/istanbul-report-html-modern/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "jsxImportSource": "react", + "types": ["vite/client"], + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "noEmit": true + }, + "include": [ + "src", + "vite.config.ts", + "vite.config.playground.ts", + "vite-plugin-report-data-dev.ts", + "report-data-placeholder.ts", + "playground/src" + ], + "exclude": [] +} diff --git a/packages/istanbul-report-html-modern/tsdown.config.ts b/packages/istanbul-report-html-modern/tsdown.config.ts new file mode 100644 index 0000000..e2e9296 --- /dev/null +++ b/packages/istanbul-report-html-modern/tsdown.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "tsdown"; + +/** React component library → `dist/index.js` + `dist/style.css` */ +export default defineConfig({ + entry: ["src/index.ts"], + platform: "neutral", + dts: true, + exports: true, + clean: true, +}); diff --git a/packages/istanbul-report-html-modern/vite-plugin-report-data-dev.ts b/packages/istanbul-report-html-modern/vite-plugin-report-data-dev.ts new file mode 100644 index 0000000..14c34b0 --- /dev/null +++ b/packages/istanbul-report-html-modern/vite-plugin-report-data-dev.ts @@ -0,0 +1,31 @@ +import fs from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import type { Plugin } from "vite"; + +import { REPORT_DATA_PLACEHOLDER } from "./report-data-placeholder.js"; + +const packageRoot = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(packageRoot, "../.."); +const mockPath = join(repoRoot, "coverage/report-data.json"); + +function readDevReportData(): string { + if (!fs.existsSync(mockPath)) { + return "null"; + } + + const reportData = JSON.parse(fs.readFileSync(mockPath, "utf-8")) as unknown; + return JSON.stringify(reportData); +} + +/** Injects mock coverage data from the repo-root coverage/report-data.json during dev. */ +export function reportDataDevPlugin(): Plugin { + return { + name: "report-data-dev", + apply: "serve", + transformIndexHtml(html) { + return html.replace(REPORT_DATA_PLACEHOLDER, readDevReportData()); + }, + }; +} diff --git a/packages/istanbul-report-html-modern/vite.config.playground.ts b/packages/istanbul-report-html-modern/vite.config.playground.ts new file mode 100644 index 0000000..3c4393c --- /dev/null +++ b/packages/istanbul-report-html-modern/vite.config.playground.ts @@ -0,0 +1,41 @@ +/// +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +const require = createRequire(import.meta.url); +const packageRoot = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(packageRoot, "../.."); +const monacoCss = join( + dirname(require.resolve("monaco-editor")), + "../../min/vs/editor/editor.main.css", +); + +/** UI playground (consumes React components from `src/`) */ +export default defineConfig({ + root: join(packageRoot, "playground"), + plugins: [react()], + resolve: { + alias: { + "@repo/fixtures": join(repoRoot, "coverage"), + "monaco-editor-css": monacoCss, + }, + }, + optimizeDeps: { + include: [ + "@ant-design/icons", + "antd", + "monaco-editor/editor/editor.api", + "monaco-editor/editor/contrib/hover/browser/hoverContribution", + "monaco-editor/languages/definitions/css/register", + "monaco-editor/languages/definitions/html/register", + "monaco-editor/languages/definitions/javascript/register", + "monaco-editor/languages/definitions/typescript/register", + "react-dom/server", + "react-highlight-words", + ], + }, +}); diff --git a/packages/istanbul-report-html-modern/vite.config.ts b/packages/istanbul-report-html-modern/vite.config.ts new file mode 100644 index 0000000..9f61ca4 --- /dev/null +++ b/packages/istanbul-report-html-modern/vite.config.ts @@ -0,0 +1,34 @@ +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; +import { viteSingleFile } from "vite-plugin-singlefile"; + +import { reportDataDevPlugin } from "./vite-plugin-report-data-dev.js"; + +const require = createRequire(import.meta.url); +const packageRoot = dirname(fileURLToPath(import.meta.url)); +const monacoCss = join( + dirname(require.resolve("monaco-editor")), + "../../min/vs/editor/editor.main.css", +); + +/** Single-file HTML report page → `dist/index.html` */ +export default defineConfig({ + root: packageRoot, + plugins: [react(), reportDataDevPlugin(), viteSingleFile()], + resolve: { + alias: { + "monaco-editor-css": monacoCss, + }, + }, + build: { + outDir: join(packageRoot, "dist"), + emptyOutDir: false, + }, + server: { + port: 51025, + }, +}); diff --git a/packages/istanbul-report-html-modern/vitest.config.ts b/packages/istanbul-report-html-modern/vitest.config.ts new file mode 100644 index 0000000..94fe224 --- /dev/null +++ b/packages/istanbul-report-html-modern/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineProject } from "vitest/config"; + +import pkg from "./package.json" with { type: "json" }; + +export default defineProject({ + test: { + name: { label: pkg.name, color: "cyan" }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7816157..9f831ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,9 @@ settings: catalogs: default: + '@ant-design/icons': + specifier: ^6.3.2 + version: 6.3.2 '@babel/core': specifier: ^8.0.1 version: 8.0.1 @@ -21,18 +24,39 @@ catalogs: '@jridgewell/trace-mapping': specifier: ^0.3.23 version: 0.3.31 + '@tsdown/css': + specifier: ^0.22.14 + version: 0.22.14 '@types/node': specifier: ^26.2.0 version: 26.2.0 + '@types/react': + specifier: ^19.2.18 + version: 19.2.18 + '@types/react-dom': + specifier: ^19.2.4 + version: 19.2.5 + '@types/react-highlight-words': + specifier: ^0.20.1 + version: 0.20.1 + '@vitejs/plugin-react': + specifier: ^6.1.0 + version: 6.1.0 '@vitest/browser-playwright': specifier: 4.1.11 version: 4.1.11 '@vitest/coverage-v8': specifier: 4.1.11 version: 4.1.11 + antd: + specifier: ^6.6.1 + version: 6.6.1 changelogithub: specifier: ^15.0.4 version: 15.0.4 + monaco-editor: + specifier: ^0.56.0 + version: 0.56.0 obug: specifier: ^2.1.4 version: 2.1.4 @@ -48,12 +72,27 @@ catalogs: preact: specifier: ^10.29.8 version: 10.29.8 + react: + specifier: ^19.2.8 + version: 19.2.8 + react-dom: + specifier: ^19.2.8 + version: 19.2.8 + react-highlight-words: + specifier: ^0.21.0 + version: 0.21.0 tsdown: specifier: ^0.22.14 version: 0.22.14 typescript: specifier: ^7.0.2 version: 7.0.2 + vite: + specifier: ^8.2.2 + version: 8.2.2 + vite-plugin-singlefile: + specifier: ^2.3.3 + version: 2.3.3 vitest: specifier: ^4.1.10 version: 4.1.11 @@ -94,7 +133,7 @@ importers: devDependencies: tsdown: specifier: 'catalog:' - version: 0.22.14(typescript@7.0.2) + version: 0.22.14(@tsdown/css@0.22.14)(typescript@7.0.2) packages/istanbul-lib-instrument: dependencies: @@ -116,7 +155,7 @@ importers: devDependencies: tsdown: specifier: 'catalog:' - version: 0.22.14(typescript@7.0.2) + version: 0.22.14(@tsdown/css@0.22.14)(typescript@7.0.2) yaml: specifier: 'catalog:' version: 2.9.0 @@ -130,6 +169,9 @@ importers: '@vitest/browser-playwright': specifier: 'catalog:' version: 4.1.11(playwright@1.62.1)(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.11) + '@vitest/istanbul-report-html-modern': + specifier: workspace:* + version: link:../istanbul-report-html-modern playwright: specifier: 'catalog:' version: 1.62.1 @@ -138,7 +180,7 @@ importers: version: 10.29.8 tsdown: specifier: 'catalog:' - version: 0.22.14(typescript@7.0.2) + version: 0.22.14(@tsdown/css@0.22.14)(typescript@7.0.2) packages/istanbul-lib-source-maps: dependencies: @@ -154,10 +196,97 @@ importers: devDependencies: tsdown: specifier: 'catalog:' - version: 0.22.14(typescript@7.0.2) + version: 0.22.14(@tsdown/css@0.22.14)(typescript@7.0.2) + + packages/istanbul-report-html-modern: + dependencies: + '@ant-design/icons': + specifier: 'catalog:' + version: 6.3.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + antd: + specifier: 'catalog:' + version: 6.6.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + monaco-editor: + specifier: 'catalog:' + version: 0.56.0 + react-highlight-words: + specifier: 'catalog:' + version: 0.21.0(react@19.2.8) + devDependencies: + '@tsdown/css': + specifier: 'catalog:' + version: 0.22.14(jiti@2.7.0)(postcss@8.5.26)(tsdown@0.22.14)(yaml@2.9.0) + '@types/node': + specifier: 'catalog:' + version: 26.2.0 + '@types/react': + specifier: 'catalog:' + version: 19.2.18 + '@types/react-dom': + specifier: 'catalog:' + version: 19.2.5(@types/react@19.2.18) + '@types/react-highlight-words': + specifier: 'catalog:' + version: 0.20.1 + '@vitejs/plugin-react': + specifier: 'catalog:' + version: 6.1.0(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0)) + react: + specifier: 'catalog:' + version: 19.2.8 + react-dom: + specifier: 'catalog:' + version: 19.2.8(react@19.2.8) + tsdown: + specifier: 'catalog:' + version: 0.22.14(@tsdown/css@0.22.14)(typescript@7.0.2) + typescript: + specifier: 'catalog:' + version: 7.0.2 + vite: + specifier: 'catalog:' + version: 8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0) + vite-plugin-singlefile: + specifier: 'catalog:' + version: 2.3.3(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0)) packages: + '@ant-design/colors@8.0.1': + resolution: {integrity: sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==} + + '@ant-design/cssinjs-utils@2.1.2': + resolution: {integrity: sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + '@ant-design/cssinjs@2.1.2': + resolution: {integrity: sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/fast-color@3.0.1': + resolution: {integrity: sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==} + engines: {node: '>=8.x'} + + '@ant-design/icons-svg@4.5.0': + resolution: {integrity: sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA==} + + '@ant-design/icons@6.3.2': + resolution: {integrity: sha512-B6O5a5XJ4wjtNOfZejXYwHW5zvKV5gYkjGf11dHGLEbKn0ABDGndo41+gfIiXyTFhvESj4XTotuud33mUFid0g==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/react-slick@2.0.0': + resolution: {integrity: sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==} + peerDependencies: + react: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@babel/code-frame@8.0.0': resolution: {integrity: sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==} engines: {node: ^22.18.0 || >=24.11.0} @@ -216,6 +345,13 @@ packages: engines: {node: ^22.18.0 || >=24.11.0} hasBin: true + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@8.0.0': + resolution: {integrity: sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==} + '@babel/template@8.0.0': resolution: {integrity: sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==} engines: {node: ^22.18.0 || >=24.11.0} @@ -250,6 +386,12 @@ packages: oxlint: optional: true + '@emotion/hash@0.8.0': + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + + '@emotion/unitless@0.7.5': + resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} + '@istanbuljs/schema@0.1.6': resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} engines: {node: '>=8'} @@ -520,6 +662,289 @@ packages: '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + '@rc-component/async-validator@6.0.0': + resolution: {integrity: sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA==} + engines: {node: '>=14.x'} + + '@rc-component/cascader@1.22.0': + resolution: {integrity: sha512-SffrA57aS9oub3VuI7ajPhJTPtaNxngSvtRhD40Rd8dwJ5vfWPSrVanWgeepdWFGBt7EHftIK5RUU0u3rCTwWw==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/checkbox@2.0.0': + resolution: {integrity: sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/collapse@1.2.0': + resolution: {integrity: sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/color-picker@3.1.1': + resolution: {integrity: sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/context@2.0.2': + resolution: {integrity: sha512-uiGpAlblCNlziHPwj4S4Iy/oemeuz/hR03mbiEjTCXwsqOIN3BOzsRMyDwpyO5Fm0vIEEJRUf9ZtbRLbhksuTA==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/dialog@1.10.0': + resolution: {integrity: sha512-eDukNlz9vNszAGv7i3zKXdxEd3wgVmNxuJijYt8zvTh17QwTu8KK/bdURRd/lU4qaMzhO1HKKmMrwOnkaw0BvQ==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/drawer@1.4.2': + resolution: {integrity: sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/dropdown@1.0.3': + resolution: {integrity: sha512-YTST/N6kpqpDz3IMuM/PSSZnrDpSOA6dgHv12gPA90ZTSLv2CoqkZ0+9NtwTY6BeO7dstPblSic2QJg7dSFy/g==} + peerDependencies: + react: '>=16.11.0' + react-dom: '>=16.11.0' + + '@rc-component/form@1.8.6': + resolution: {integrity: sha512-1EXVsSKPZC6kGrIqxVck7kAWM6T65b6et/n5wIcyiaIa41VrDshD0nVLHoBDEIZr2ATQsOP6icb4XQkNFMLBAw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/image@1.10.0': + resolution: {integrity: sha512-BjeZCRQ+hw+4WAhvrw8rJvy5fckA2xpf/X2XQEOABUHvLTNB9inB98X3Mp54jYQ7g10DfWERQWHXeC4ylxp1Uw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/input-number@1.6.2': + resolution: {integrity: sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/input@1.3.1': + resolution: {integrity: sha512-iFvTUT9W+JC/MSin2aGAk8NqsVlTzcExNC9DZariON1IWirju9NoNeEk47an4Q8iHazkoVI/y1LnDi88+CPcig==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@rc-component/listy@1.2.3': + resolution: {integrity: sha512-IXiMjV5s0rczLBlfh7G5nB4M3365mrEeedjwKtf5I+Ns3PqRUsebR2h5u8CeFarsVfLUPC2I5p0h09TNoOWyvQ==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/mentions@1.11.0': + resolution: {integrity: sha512-IC2qXuEBMFHxPIXEFfYWj6Sr7UiDZnOqJHCYQBbwPzopBJOPZIR6mV9U4QH1bYQRlKYlYnIsajWDMgVGgWQyWQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/menu@1.4.1': + resolution: {integrity: sha512-3GsVRoQ4cnF/AoIQ4P+Z1haBfgfBPQfLT1RJY3Nu4DzOnheTslfCiGSPj7bv/cLj5sW5pHqN25dDXGP3JELAlQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mini-decimal@1.1.4': + resolution: {integrity: sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w==} + engines: {node: '>=8.x'} + + '@rc-component/motion@1.3.3': + resolution: {integrity: sha512-Xh3IszxvlSv3/PLYFyC2UZi9LNB83yOnkB/LNmRzaypZLvkhqUIPS7MQpGZcCMWrNsXV2p6YTSWbSGvFpEle9A==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mutate-observer@2.0.1': + resolution: {integrity: sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/notification@2.0.7': + resolution: {integrity: sha512-nqZzpf6BPdaj+3ILx7si79LLmqPKyUmQoXa+/9gg0SkH0v1DbD66oJgRMSBEVnd/zUT3D4gwxWIHUKebYf2ZXQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/overflow@1.0.1': + resolution: {integrity: sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/pagination@1.4.0': + resolution: {integrity: sha512-CW1g7P9V8u+e8JQdUsl2RWg+GCsoee0mtJjZUCCxn/vb3jzOwDKm6hAdwddHCVBfWJ58eGUBZz3IvnU8rRktjw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/picker@1.12.0': + resolution: {integrity: sha512-0FZGgDiDZFMm2hcfGv4jyjm7yWIj2MiwfeTzLL0vWkO+8cCSKdiM9XhWpkn1aAsoI2EwUy48Oz2cGfvL8ZKrfw==} + engines: {node: '>=12.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + + '@rc-component/portal@2.2.1': + resolution: {integrity: sha512-ck+r1kW/JSv0wxPji3KN2ss9K6Z0qqwusw/mf/0JobXhZ8hC2ejZwCJObW/SvDi0uhA0VzmCnx0CaCci95tcmA==} + engines: {node: '>=12.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/progress@1.0.2': + resolution: {integrity: sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/qrcode@2.0.0': + resolution: {integrity: sha512-aAv3QhPP1xyafuTZOxub6a54pCeBnN3IwQkpETrBtthq4BL5IgxnCbuoBWPDpdLw1y1j6BgBUCAKV92+yX06Dw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/rate@1.0.1': + resolution: {integrity: sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/resize-observer@1.1.2': + resolution: {integrity: sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/segmented@1.3.0': + resolution: {integrity: sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@rc-component/select@1.10.1': + resolution: {integrity: sha512-H+yQsl+qED9NilQ3g6zdpsMwUgwVjrcMTkNHAWRVU/MoNCYgTbDgU+MIMgZDK+rVdd2JUfI/MkysMcZZ0cyQKw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + + '@rc-component/slider@1.1.1': + resolution: {integrity: sha512-LSzgWGYDgeCDgR4r1XlU29gbYws6HpLnvJd/uMhLeW/vQgxldeR+Wb4uzHDCHiYEbr1bnEHWdjkPxjJRHxuiig==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/steps@1.2.2': + resolution: {integrity: sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/switch@1.0.3': + resolution: {integrity: sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/table@1.11.1': + resolution: {integrity: sha512-OWdS6DMmeWb7bJBGqPxYZpQbzBlBiXZUu2sqo6Ii7Sjs9GeK1IsrXrWk26SL2c6KEseabswdxrRj7WUm9LdECw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/tabs@1.12.0': + resolution: {integrity: sha512-XL7Kqy5fnUE2WTlO1/fCGrrfNlGFebdr7JseGkEIjzcVMAtIFQJ8sqCSOmxcXstjU6fonD/4rnhZHxj7sDTajQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tooltip@1.5.0': + resolution: {integrity: sha512-agQ/+mBqrEQfTX4D3KhQ7j+ZbX4/VHjoJ7Noa2wIdZ1/FbQTOd7Sn92rp+jtCoqAVTLUgSOydePIgZ204gi2EQ==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/tour@2.4.0': + resolution: {integrity: sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tree-select@1.16.1': + resolution: {integrity: sha512-a1Oi6EJhqAhdOxxupdJi6fP0RPHMKn5TcfkX2+llaQ4lF4nwfH7b6SCHcnsybaa2s+pk1yZYwVyeOYkDnEBRdg==} + peerDependencies: + react: '*' + react-dom: '*' + + '@rc-component/tree@1.4.0': + resolution: {integrity: sha512-dGsJGDJQedA0BqqVgj3F8BvHXTSZijyhTXdbAdkcx8lynzZkty/CV3Z3LOm/fxz+BCfl3dfGiAQpb7Q5XNvl0Q==} + engines: {node: '>=10.x'} + peerDependencies: + react: '*' + react-dom: '*' + + '@rc-component/trigger@3.10.1': + resolution: {integrity: sha512-mXlDN0IXdtV8Yqqm8195ECCyrbmfvvfKvwVvSlH0+qvKD6BUF8gRhEjSy0FOcD1+CcDRHgTiX99LoxfQrmh3Cw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/upload@1.1.1': + resolution: {integrity: sha512-GvYWSKeaJTOxxC5p6+nOSadzfvXA1h8C/iHFPFZX+szH3JUXrvs+DLiW8YUTBgvMh8m63mJeHrlYlJzAlg+pDA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/util@1.12.0': + resolution: {integrity: sha512-AEjPL8JVdohIITaiXokyjL9WQ6tKWWjAYK9QU16tGNE9JaQABBQy+hA4H2Lup5MgXy9yY3iLrbZJheuU13hTdQ==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/virtual-list@1.5.1': + resolution: {integrity: sha512-boqHxdtyWC88u8quYgEO49bcBy5fzRiOcnBge+N4nLzs2k8hUQ/yw7JE9dM6yCBE4jSm5YSHVCVMS+suBuJGKA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + '@rolldown/binding-android-arm-eabi@1.2.5': resolution: {integrity: sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -622,6 +1047,28 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@tsdown/css@0.22.14': + resolution: {integrity: sha512-0NpbgqfhfjaAoLdY3DxjrMo08vKFEtvlybCS42Gtolx8OPoKUdZZDyls0qgNEqea0c7XGjlF0NIE1tIxgU8RqA==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + postcss: ^8.4.0 + postcss-import: ^16.0.0 + postcss-modules: ^6.0.0 + sass: '*' + sass-embedded: '*' + tsdown: 0.22.14 + peerDependenciesMeta: + postcss: + optional: true + postcss-import: + optional: true + postcss-modules: + optional: true + sass: + optional: true + sass-embedded: + optional: true + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -640,6 +1087,20 @@ packages: '@types/node@26.2.0': resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==} + '@types/react-dom@19.2.5': + resolution: {integrity: sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react-highlight-words@0.20.1': + resolution: {integrity: sha512-EgF6RaoibBurIhxk3X3d5xL0uMqjD7KtShvZ8S+d7/o6xZ+mQ4rct1a2E49vWA9r63OzrHCIyghqvQHASb/ERw==} + + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@typescript/typescript-aix-ppc64@7.0.2': resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} engines: {node: '>=16.20.0'} @@ -760,6 +1221,22 @@ packages: cpu: [x64] os: [win32] + '@vitejs/plugin-react@6.1.0': + resolution: {integrity: sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + oxc-transform-react: ^0.145.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + oxc-transform-react: + optional: true + '@vitest/browser-playwright@4.1.11': resolution: {integrity: sha512-riLBxPqwnJ0lWs2DN2WeUfYeKLoAjbP2Xx8cLQdSddzMi20sksIa6K2mPz79DyMZKKVKH2ksOC2yJvtNcZg8cg==} peerDependencies: @@ -953,6 +1430,12 @@ packages: resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} + antd@6.6.1: + resolution: {integrity: sha512-QHIHYoUk9N9nJy1T9fyxWKjY0qApdTEDd/6lzqYng8Uryv9FejNmbhKvYF7obGqB+TuLXQsPVF7fOVgyzM1KrQ==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1037,9 +1520,16 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -1056,6 +1546,12 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dayjs@1.11.23: + resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==} + default-browser-id@5.0.1: resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} engines: {node: '>=18'} @@ -1078,6 +1574,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dompurify@3.4.8: + resolution: {integrity: sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ==} + dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} @@ -1170,6 +1669,9 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + highlight-words-core@1.2.3: + resolution: {integrity: sha512-m1O9HW3/GNHxzSIXWw1wCNXXsgLlxrP0OI6+ycGUhiUHkikqW3OrwVHz+lxeNBe5yqLESdIcj8PowHQ2zLvUvQ==} + hookable@6.1.1: resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} @@ -1205,6 +1707,9 @@ packages: engines: {node: '>=14.16'} hasBin: true + is-mobile@5.0.0: + resolution: {integrity: sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1241,6 +1746,9 @@ packages: engines: {node: '>=6'} hasBin: true + json2mq@0.2.0: + resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -1320,6 +1828,10 @@ packages: resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} engines: {node: '>= 12.0.0'} + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + lru-cache@11.5.2: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} @@ -1334,6 +1846,18 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} + engines: {node: '>= 18'} + hasBin: true + + memoize-one@4.0.3: + resolution: {integrity: sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + minipass@3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} @@ -1357,6 +1881,9 @@ packages: module-replacements@3.2.0: resolution: {integrity: sha512-zaDcWtW+kLmYGWCMWAyQ8Je333BYmsBMB2X6q5MKBCI697l7OawNaC5TQqfgPVxhdXBA1AjumQjPMcHyJe1f2A==} + monaco-editor@0.56.0: + resolution: {integrity: sha512-sXboRm3BeBeLm938eaiyLMe0OxzfXIlZvbv4ir/jVgQy1zDhWjgmny0WoN45fuDKhCCQsYMbBJrv/A6jd8aCUg==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -1472,6 +1999,24 @@ packages: resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} engines: {node: '>=14.19.0'} + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + postcss@8.5.26: resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} engines: {node: ^10 || ^12 || >=14} @@ -1493,6 +2038,23 @@ packages: rc9@3.0.1: resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} + react-dom@19.2.8: + resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} + peerDependencies: + react: ^19.2.8 + + react-highlight-words@0.21.0: + resolution: {integrity: sha512-SdWEeU9fIINArEPO1rO5OxPyuhdEKZQhHzZZP1ie6UeXQf+CjycT1kWaB+9bwGcVbR0NowuHK3RqgqNg6bgBDQ==} + peerDependencies: + react: ^0.14.0 || ^15.0.0 || ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0 + + react-is@19.2.8: + resolution: {integrity: sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==} + + react@19.2.8: + resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} + engines: {node: '>=0.10.0'} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -1532,6 +2094,12 @@ packages: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} @@ -1560,6 +2128,12 @@ packages: std-env@4.2.0: resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + string-convert@0.2.1: + resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} + + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1569,6 +2143,10 @@ packages: engines: {node: '>=10'} deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1661,6 +2239,16 @@ packages: resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} engines: {node: '>=18.12.0'} + vite-plugin-singlefile@2.3.3: + resolution: {integrity: sha512-XVnGH0QzbOa8fxRSsHdCarVN1BSBXNi7uLMQYlrGRN5apdHkk62XQWRJhVever0lnfuyBkwn+kvVChdm/OoOUg==} + engines: {node: '>18.0.0'} + peerDependencies: + rollup: ^4.59.0 + vite: ^5.4.21 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + rollup: + optional: true + vite@8.2.2: resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1785,6 +2373,52 @@ packages: snapshots: + '@ant-design/colors@8.0.1': + dependencies: + '@ant-design/fast-color': 3.0.1 + + '@ant-design/cssinjs-utils@2.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@ant-design/cssinjs': 2.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@babel/runtime': 7.29.7 + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@ant-design/cssinjs@2.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@babel/runtime': 7.29.7 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + csstype: 3.2.3 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + stylis: 4.4.0 + + '@ant-design/fast-color@3.0.1': {} + + '@ant-design/icons-svg@4.5.0': {} + + '@ant-design/icons@6.3.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@ant-design/colors': 8.0.1 + '@ant-design/icons-svg': 4.5.0 + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@ant-design/react-slick@2.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@babel/runtime': 7.29.7 + clsx: 2.1.1 + json2mq: 0.2.0 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + throttle-debounce: 5.0.2 + '@babel/code-frame@8.0.0': dependencies: '@babel/helper-validator-identifier': 8.0.4 @@ -1853,6 +2487,10 @@ snapshots: dependencies: '@babel/types': 8.0.4 + '@babel/runtime@7.29.7': {} + + '@babel/runtime@8.0.0': {} + '@babel/template@8.0.0': dependencies: '@babel/code-frame': 8.0.0 @@ -1891,6 +2529,10 @@ snapshots: optionalDependencies: oxlint: 1.79.0 + '@emotion/hash@0.8.0': {} + + '@emotion/unitless@0.7.5': {} + '@istanbuljs/schema@0.1.6': {} '@jridgewell/gen-mapping@0.3.13': @@ -2029,6 +2671,354 @@ snapshots: dependencies: quansync: 1.0.0 + '@rc-component/async-validator@6.0.0': + dependencies: + '@babel/runtime': 7.29.7 + + '@rc-component/cascader@1.22.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/select': 1.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/tree': 1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/checkbox@2.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/collapse@1.2.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/color-picker@3.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@ant-design/fast-color': 3.0.1 + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/context@2.0.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/dialog@1.10.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/portal': 2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/drawer@1.4.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/portal': 2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/dropdown@1.0.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/form@1.8.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/async-validator': 6.0.0 + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/image@1.10.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/portal': 2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/input-number@1.6.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/mini-decimal': 1.1.4 + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/input@1.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/listy@1.2.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/portal': 2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/virtual-list': 1.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/mentions@1.11.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/input': 1.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/menu': 1.4.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/menu@1.4.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/overflow': 1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/mini-decimal@1.1.4': + dependencies: + '@babel/runtime': 7.29.7 + + '@rc-component/motion@1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/mutate-observer@2.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/notification@2.0.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/overflow@1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/pagination@1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/picker@1.12.0(dayjs@1.11.23)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/overflow': 1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + dayjs: 1.11.23 + + '@rc-component/portal@2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/progress@1.0.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/qrcode@2.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@babel/runtime': 7.29.7 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/rate@1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/resize-observer@1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/segmented@1.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@babel/runtime': 7.29.7 + '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/select@1.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/overflow': 1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/virtual-list': 1.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/slider@1.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/steps@1.2.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/switch@1.0.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/table@1.11.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/context': 2.0.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/virtual-list': 1.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/tabs@1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/dropdown': 1.0.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/menu': 1.4.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/tooltip@1.5.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/tour@2.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/portal': 2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/tree-select@1.16.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/select': 1.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/tree': 1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/tree@1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/virtual-list': 1.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/trigger@3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/portal': 2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/upload@1.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + + '@rc-component/util@1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + is-mobile: 5.0.0 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-is: 19.2.8 + + '@rc-component/virtual-list@1.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@babel/runtime': 8.0.0 + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + '@rolldown/binding-android-arm-eabi@1.2.5': optional: true @@ -2078,6 +3068,19 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@tsdown/css@0.22.14(jiti@2.7.0)(postcss@8.5.26)(tsdown@0.22.14)(yaml@2.9.0)': + dependencies: + lightningcss: 1.33.0 + postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.26)(yaml@2.9.0) + rolldown: 1.2.5 + tsdown: 0.22.14(@tsdown/css@0.22.14)(typescript@7.0.2) + optionalDependencies: + postcss: 8.5.26 + transitivePeerDependencies: + - jiti + - tsx + - yaml + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -2095,6 +3098,21 @@ snapshots: dependencies: undici-types: 8.3.0 + '@types/react-dom@19.2.5(@types/react@19.2.18)': + dependencies: + '@types/react': 19.2.18 + + '@types/react-highlight-words@0.20.1': + dependencies: + '@types/react': 19.2.18 + + '@types/react@19.2.18': + dependencies: + csstype: 3.2.3 + + '@types/trusted-types@2.0.7': + optional: true + '@typescript/typescript-aix-ppc64@7.0.2': optional: true @@ -2155,6 +3173,11 @@ snapshots: '@typescript/typescript-win32-x64@7.0.2': optional: true + '@vitejs/plugin-react@6.1.0(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0) + '@vitest/browser-playwright@4.1.11(playwright@1.62.1)(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.11)': dependencies: '@vitest/browser': 4.1.11(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.11) @@ -2320,6 +3343,63 @@ snapshots: ansis@4.3.1: {} + antd@6.6.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + dependencies: + '@ant-design/colors': 8.0.1 + '@ant-design/cssinjs': 2.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@ant-design/cssinjs-utils': 2.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@ant-design/fast-color': 3.0.1 + '@ant-design/icons': 6.3.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@ant-design/react-slick': 2.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@babel/runtime': 7.29.7 + '@rc-component/cascader': 1.22.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/checkbox': 2.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/collapse': 1.2.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/color-picker': 3.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/dialog': 1.10.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/drawer': 1.4.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/dropdown': 1.0.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/form': 1.8.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/image': 1.10.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/input': 1.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/input-number': 1.6.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/listy': 1.2.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/mentions': 1.11.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/menu': 1.4.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/mutate-observer': 2.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/notification': 2.0.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/pagination': 1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/picker': 1.12.0(dayjs@1.11.23)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/progress': 1.0.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/qrcode': 2.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/rate': 1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/segmented': 1.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/select': 1.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/slider': 1.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/steps': 1.2.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/switch': 1.0.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/table': 1.11.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/tabs': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/tooltip': 1.5.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/tour': 2.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/tree': 1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/tree-select': 1.16.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/upload': 1.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + clsx: 2.1.1 + dayjs: 1.11.23 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.2 + transitivePeerDependencies: + - date-fns + - luxon + - moment + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -2444,8 +3524,12 @@ snapshots: dependencies: consola: 3.4.2 + clsx@2.1.1: {} + colorette@2.0.20: {} + compute-scroll-into-view@3.1.1: {} + confbox@0.1.8: {} confbox@0.2.4: {} @@ -2456,6 +3540,10 @@ snapshots: convert-source-map@2.0.0: {} + csstype@3.2.3: {} + + dayjs@1.11.23: {} + default-browser-id@5.0.1: {} default-browser@5.5.1: @@ -2471,6 +3559,10 @@ snapshots: detect-libc@2.1.2: {} + dompurify@3.4.8: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dotenv@16.6.1: {} dotenv@17.4.2: {} @@ -2535,6 +3627,8 @@ snapshots: has-flag@4.0.0: {} + highlight-words-core@1.2.3: {} + hookable@6.1.1: {} html-escaper@2.0.2: {} @@ -2559,6 +3653,8 @@ snapshots: dependencies: is-docker: 3.0.0 + is-mobile@5.0.0: {} + is-number@7.0.0: {} is-wsl@3.1.1: @@ -2586,6 +3682,10 @@ snapshots: jsesc@3.1.0: {} + json2mq@0.2.0: + dependencies: + string-convert: 0.2.1 + json5@2.2.3: {} lightningcss-android-arm64@1.33.0: @@ -2637,6 +3737,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.33.0 lightningcss-win32-x64-msvc: 1.33.0 + lilconfig@3.1.3: {} + lru-cache@11.5.2: {} magic-string@0.30.21: @@ -2653,6 +3755,15 @@ snapshots: dependencies: semver: 7.8.5 + marked@14.0.0: {} + + memoize-one@4.0.3: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + minipass@3.3.6: dependencies: yallist: 4.0.0 @@ -2675,6 +3786,11 @@ snapshots: module-replacements@3.2.0: {} + monaco-editor@0.56.0: + dependencies: + dompurify: 3.4.8 + marked: 14.0.0 + mri@1.2.0: {} mrmime@2.0.1: {} @@ -2797,6 +3913,14 @@ snapshots: pngjs@7.0.0: {} + postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.26)(yaml@2.9.0): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 2.7.0 + postcss: 8.5.26 + yaml: 2.9.0 + postcss@8.5.26: dependencies: nanoid: 3.3.18 @@ -2817,6 +3941,21 @@ snapshots: defu: 6.1.7 destr: 2.0.5 + react-dom@19.2.8(react@19.2.8): + dependencies: + react: 19.2.8 + scheduler: 0.27.0 + + react-highlight-words@0.21.0(react@19.2.8): + dependencies: + highlight-words-core: 1.2.3 + memoize-one: 4.0.3 + react: 19.2.8 + + react-is@19.2.8: {} + + react@19.2.8: {} + readdirp@3.6.0: dependencies: picomatch: 2.3.2 @@ -2862,6 +4001,12 @@ snapshots: run-applescript@7.1.0: {} + scheduler@0.27.0: {} + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + scule@1.3.0: {} semver@7.8.5: {} @@ -2882,6 +4027,10 @@ snapshots: std-env@4.2.0: {} + string-convert@0.2.1: {} + + stylis@4.4.0: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -2895,6 +4044,8 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + throttle-debounce@5.0.2: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -2918,7 +4069,7 @@ snapshots: tree-kill@1.2.2: {} - tsdown@0.22.14(typescript@7.0.2): + tsdown@0.22.14(@tsdown/css@0.22.14)(typescript@7.0.2): dependencies: ansis: 4.3.1 cac: 7.0.0 @@ -2936,6 +4087,7 @@ snapshots: unconfig-core: 7.5.0 verkit: 0.3.2 optionalDependencies: + '@tsdown/css': 0.22.14(jiti@2.7.0)(postcss@8.5.26)(tsdown@0.22.14)(yaml@2.9.0) typescript: 7.0.2 transitivePeerDependencies: - '@typescript/native-preview' @@ -2983,6 +4135,11 @@ snapshots: verkit@0.3.2: {} + vite-plugin-singlefile@2.3.3(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0)): + dependencies: + micromatch: 4.0.8 + vite: 8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0) + vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e11566e..3b5b70f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,6 +12,7 @@ packages: catalog: "@types/node": ^26.2.0 tsdown: ^0.22.14 + "@tsdown/css": ^0.22.14 "@babel/core": ^8.0.1 "@babel/parser": ^8.0.4 "@istanbuljs/schema": ^0.1.3 @@ -27,4 +28,16 @@ catalog: "@vitest/coverage-v8": 4.1.11 typescript: ^7.0.2 "@e18e/eslint-plugin": ^0.8.0 - changelogithub: ^15.0.4 \ No newline at end of file + changelogithub: ^15.0.4 + "@ant-design/icons": ^6.3.2 + antd: ^6.6.1 + monaco-editor: ^0.56.0 + react: ^19.2.8 + react-dom: ^19.2.8 + react-highlight-words: ^0.21.0 + "@types/react": ^19.2.18 + "@types/react-dom": ^19.2.4 + "@types/react-highlight-words": ^0.20.1 + "@vitejs/plugin-react": ^6.1.0 + vite: ^8.2.2 + vite-plugin-singlefile: ^2.3.3 diff --git a/vitest.config.ts b/vitest.config.ts index ea17b5b..9eae8cf 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,4 +1,7 @@ +import { join } from "node:path"; + import { defineConfig } from "vitest/config"; +const htmlModernReporter = join(process.cwd(), "html-modern.cjs"); export default defineConfig({ test: { @@ -6,8 +9,8 @@ export default defineConfig({ enabled: true, provider: "v8", include: ["packages/**/src/**/*.ts"], - reporter: ["json"], + reporter: ["json", [htmlModernReporter, { writeReportDataJson: true }]], }, - projects: ["packages/*"], + projects: ["packages/*/vitest.config.*"], }, }); From 319e5612f0f8fc7e496ca3556ea00284651829ff Mon Sep 17 00:00:00 2001 From: Travis Zhang Date: Wed, 2 Sep 2026 14:41:19 +0800 Subject: [PATCH 06/15] refactor: rename options variable and clean up path handling functions --- packages/istanbul-lib-report/src/index.ts | 1 - packages/istanbul-lib-report/src/path.ts | 4 + .../src/reports/html-modern/index.ts | 12 +-- .../reports/html-modern/infer-project-root.ts | 74 ++++++++----------- .../src/reports/html-modern/options.ts | 4 +- .../src/reports/html-modern/types.ts | 3 - .../src/summarizer-factory.ts | 6 +- 7 files changed, 42 insertions(+), 62 deletions(-) diff --git a/packages/istanbul-lib-report/src/index.ts b/packages/istanbul-lib-report/src/index.ts index c902ebd..14946c9 100644 --- a/packages/istanbul-lib-report/src/index.ts +++ b/packages/istanbul-lib-report/src/index.ts @@ -53,7 +53,6 @@ export type { CoberturaOptions } from "./reports/cobertura/index"; export type { HtmlSpaOptions } from "./reports/html-spa/index"; export type { CoverageData, - CovData, GenerateOptions, GenerateResult, HtmlModernOptions, diff --git a/packages/istanbul-lib-report/src/path.ts b/packages/istanbul-lib-report/src/path.ts index 4eef6fb..1accb84 100644 --- a/packages/istanbul-lib-report/src/path.ts +++ b/packages/istanbul-lib-report/src/path.ts @@ -131,6 +131,10 @@ class Path { return new Path(ret); } + static findCommonParent(paths: Path[]): Path { + return paths.reduce((common, path) => common.commonPrefixPath(path), paths[0] ?? new Path([])); + } + static compare(a: Path, b: Path): number { const al = a.length; const bl = b.length; diff --git a/packages/istanbul-lib-report/src/reports/html-modern/index.ts b/packages/istanbul-lib-report/src/reports/html-modern/index.ts index 34b2b11..bbf6c09 100644 --- a/packages/istanbul-lib-report/src/reports/html-modern/index.ts +++ b/packages/istanbul-lib-report/src/reports/html-modern/index.ts @@ -14,7 +14,6 @@ export type { LinkMapper } from "../html/index"; export type { HtmlModernOptions } from "./options"; export type { CoverageData, - CovData, GenerateOptions, GenerateResult, IstanbulReportContext, @@ -27,19 +26,19 @@ export { extractIstanbulContext } from "./istanbul-context"; export { inferProjectRoot, resolveProjectRoot } from "./infer-project-root"; class HtmlModernReport extends ReportBase { - private options: HtmlModernOptions; + private htmlOptions: HtmlModernOptions; private coverage: Record = {}; private summarizer?: Summarizers; constructor(opts: HtmlModernOptions & Partial = {}) { super(opts); - this.options = opts; - this.coverage = {}; + this.htmlOptions = opts; if (opts.summarizer !== undefined) { this.summarizer = opts.summarizer; } } + /** Called for each file node during tree traversal; stores a deep clone keyed by path. */ onDetail(node: ReportNode): void { const fileCoverage: FileCoverageData = JSON.parse( JSON.stringify(node.getFileCoverage().toJSON()), @@ -47,9 +46,10 @@ class HtmlModernReport extends ReportBase { this.coverage[fileCoverage.path] = fileCoverage; } + /** Called after traversal completes; generates the HTML report via {@link CoverageReport}. */ async onEnd(_rootNode: ReportNode, context: Context): Promise { - const cr = new CoverageReport(this.options); - await cr.generate({ + const coverageReport = new CoverageReport(this.htmlOptions); + await coverageReport.generate({ coverage: this.coverage, targetDir: context.dir, sourceFinder: context.sourceFinder, diff --git a/packages/istanbul-lib-report/src/reports/html-modern/infer-project-root.ts b/packages/istanbul-lib-report/src/reports/html-modern/infer-project-root.ts index 7bb8e99..1cfc8a8 100644 --- a/packages/istanbul-lib-report/src/reports/html-modern/infer-project-root.ts +++ b/packages/istanbul-lib-report/src/reports/html-modern/infer-project-root.ts @@ -1,67 +1,51 @@ -/** Parent directory with POSIX slashes (empty string if none / only filesystem root). */ -function posixParentDir(filePath: string): string { - const posix = filePath.replaceAll("\\", "/").replace(/\/+$/, ""); - const idx = posix.lastIndexOf("/"); - // Match SummarizerFactory: file directly under `/` has an empty parent path. - if (idx <= 0) { - return ""; +import Path from "../../path"; + +/** Map a coverage file path to {@link Path}, including Windows drive paths on any platform. */ +function filePathToPath(filePath: string): Path { + const normalized = filePath.replaceAll("\\", "/"); + if (/^[A-Za-z]:(?:\/|$)/.test(normalized)) { + const withoutDrive = normalized.replace(/^[A-Za-z]:\/?/, ""); + const segments = withoutDrive ? withoutDrive.split("/").filter(Boolean) : []; + return new Path(segments); } - return posix.slice(0, idx); + + return new Path(filePath); } -function commonPrefixSegments(paths: string[]): string[] { - if (paths.length === 0) { - return []; - } +/** Restore a filesystem path prefix dropped by {@link Path} (POSIX `/` or Windows drive). */ +function formatProjectRoot(common: Path, filePaths: string[]): string { + const relative = common.toString(); + const sample = filePaths[0]!.replaceAll("\\", "/"); - const segmentsList = paths.map((p) => { - if (p === "") { - return []; - } - if (p === "/") { - return [""]; - } - return p.split("/"); - }); + const driveMatch = sample.match(/^([A-Za-z]:)(?:\/|$)/); + if (driveMatch) { + return relative ? `${driveMatch[1]}/${relative}` : driveMatch[1]; + } - let common = segmentsList[0]!; - for (let i = 1; i < segmentsList.length; i++) { - const other = segmentsList[i]!; - const next: string[] = []; - const len = Math.min(common.length, other.length); - for (let j = 0; j < len; j++) { - if (common[j] === other[j]) { - next.push(common[j]!); - } else { - break; - } - } - common = next; - if (common.length === 0) { - break; - } + if (sample.startsWith("/")) { + return relative ? `/${relative}` : "/"; } - return common; + return relative; } /** - * Infer project root like SummarizerFactory's `_commonParent`: - * the common parent directory of all coverage file paths. - * Returns `undefined` when there is no useful shared prefix (including filesystem root only). + * Infer project root from coverage file paths: the common parent directory + * of all file paths (same algorithm as SummarizerFactory `_commonParent`). + * Returns `undefined` when there is no useful shared prefix. */ export function inferProjectRoot(filePaths: string[]): string | undefined { if (filePaths.length === 0) { return undefined; } - const common = commonPrefixSegments(filePaths.map(posixParentDir)); - // Empty or only `""` (POSIX `/`) is not a useful project root for relativizing paths. - if (common.length === 0 || (common.length === 1 && common[0] === "")) { + const parentPaths = filePaths.map((filePath) => filePathToPath(filePath).parent()); + const common = Path.findCommonParent(parentPaths); + if (common.length === 0) { return undefined; } - return common.join("/"); + return formatProjectRoot(common, filePaths); } export function resolveProjectRoot(filePaths: string[], explicit?: string): string { diff --git a/packages/istanbul-lib-report/src/reports/html-modern/options.ts b/packages/istanbul-lib-report/src/reports/html-modern/options.ts index 6b48a52..7cfbafa 100644 --- a/packages/istanbul-lib-report/src/reports/html-modern/options.ts +++ b/packages/istanbul-lib-report/src/reports/html-modern/options.ts @@ -14,8 +14,8 @@ export interface HtmlModernOptions { metricsToShow?: ("lines" | "branches" | "functions" | "statements")[]; /** * Project root used to relativize coverage paths in the UI. - * Defaults to the common parent of coverage file directories (same idea as - * SummarizerFactory `_commonParent`), falling back to `process.cwd()`. + * Defaults to the common parent of coverage file directories (see + * {@link inferProjectRoot}), falling back to `process.cwd()`. */ projectRoot?: string; /** write `report-data.json` alongside `index.html` in the report output directory */ diff --git a/packages/istanbul-lib-report/src/reports/html-modern/types.ts b/packages/istanbul-lib-report/src/reports/html-modern/types.ts index f549582..da416b0 100644 --- a/packages/istanbul-lib-report/src/reports/html-modern/types.ts +++ b/packages/istanbul-lib-report/src/reports/html-modern/types.ts @@ -51,9 +51,6 @@ export interface ReportData { packageVersion?: string; } -/** @deprecated use {@link ReportData} */ -export type CovData = ReportData; - /** output from {@link CoverageReport.generate} */ export interface GenerateResult { reportData: ReportData; diff --git a/packages/istanbul-lib-report/src/summarizer-factory.ts b/packages/istanbul-lib-report/src/summarizer-factory.ts index 1304620..096c73d 100644 --- a/packages/istanbul-lib-report/src/summarizer-factory.ts +++ b/packages/istanbul-lib-report/src/summarizer-factory.ts @@ -145,10 +145,6 @@ interface InitialListNode { fileCoverage: FileCoverage; } -function findCommonParent(paths: Path[]): Path { - return paths.reduce((common, path) => common.commonPrefixPath(path), paths[0] || new Path([])); -} - function findOrCreateParent( parentPath: Path, nodeMap: Record, @@ -231,7 +227,7 @@ class SummarizerFactory { path: new Path(filePath), fileCoverage: coverageMap.fileCoverageFor(filePath), })); - this._commonParent = findCommonParent(this._initialList.map((o) => o.path.parent())); + this._commonParent = Path.findCommonParent(this._initialList.map((o) => o.path.parent())); if (this._commonParent.length > 0) { this._initialList.forEach((o) => { o.path.splice(0, this._commonParent.length); From 5d5d37d5162ed2c3f3d4a5e4fc3d27037362ccdb Mon Sep 17 00:00:00 2001 From: Travis Zhang Date: Wed, 2 Sep 2026 14:54:40 +0800 Subject: [PATCH 07/15] chore: configure server port for playground environment --- packages/istanbul-report-html-modern/vite.config.playground.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/istanbul-report-html-modern/vite.config.playground.ts b/packages/istanbul-report-html-modern/vite.config.playground.ts index 3c4393c..57353f2 100644 --- a/packages/istanbul-report-html-modern/vite.config.playground.ts +++ b/packages/istanbul-report-html-modern/vite.config.playground.ts @@ -38,4 +38,7 @@ export default defineConfig({ "react-highlight-words", ], }, + server: { + port: 30614, + }, }); From b6ce9e41d552723da0136416f20fa825e392c7ea Mon Sep 17 00:00:00 2001 From: Travis Zhang Date: Wed, 2 Sep 2026 16:24:22 +0800 Subject: [PATCH 08/15] refactor: reorganize project structure and update imports for modern HTML report --- .../src/reports/html-modern/write-report.ts | 5 ++- .../istanbul-report-html-modern/README.md | 20 ++++++--- .../playground/src/App.tsx | 8 ++-- .../src/{ => lib}/Report.tsx | 0 .../src/{ => lib}/ReportApp.tsx | 0 .../{ => lib}/components/CoverageDetail.tsx | 0 .../src/{ => lib}/components/LineNumbers.tsx | 0 .../{ => lib}/components/SummaryHeader.tsx | 0 .../src/{ => lib}/components/SummaryList.tsx | 0 .../src/{ => lib}/components/SummaryTree.tsx | 0 .../src/{ => lib}/components/TopControl.tsx | 0 .../src/{ => lib}/css.d.ts | 0 .../src/{ => lib}/hash-route.ts | 0 .../src/{ => lib}/helpers/annotate.ts | 0 .../{ => lib}/helpers/build-report-files.ts | 0 .../src/{ => lib}/helpers/color.ts | 0 .../src/{ => lib}/helpers/derive-views.ts | 0 .../src/{ => lib}/helpers/empty-coverage.ts | 0 .../src/{ => lib}/helpers/line-hits.ts | 0 .../src/{ => lib}/helpers/summary.ts | 0 .../src/{ => lib}/icons/TreeViewIcon.tsx | 0 .../src/{ => lib}/index.css | 0 .../src/{ => lib}/index.ts | 1 + .../src/{ => lib}/monaco-esm.d.ts | 0 .../src/{ => lib}/monaco.ts | 0 .../src/{ => lib}/paths.ts | 0 .../src/{ => lib}/theme-context.tsx | 0 .../src/{ => lib}/types.ts | 0 .../src/{ => lib}/useHashPath.ts | 0 .../src/{ => lib}/vite-env.d.ts | 0 .../{ => page}/components/ReportFooter.css | 0 .../{ => page}/components/ReportFooter.tsx | 0 .../src/{helpers => page}/decompress.ts | 0 .../{ => src/page}/index.html | 2 +- .../loadData.ts => page/load-data.ts} | 15 ++++++- .../src/{ => page}/main.tsx | 8 ++-- .../src/{ => page}/report-data.ts | 0 .../src/{ReportPage.tsx => page/shell.tsx} | 23 +++++------ .../src/page/vite-env.d.ts | 10 +++++ .../test/paths.test.ts | 7 +++- .../test/summary.test.ts | 10 ++--- .../istanbul-report-html-modern/tsconfig.json | 3 +- .../tsdown.config.ts | 2 +- .../vite-plugin-report-data-dev.ts | 23 ++++++----- .../vite.config.playground.ts | 24 ++++++++--- .../vite.config.ts | 41 +++++++++++++++---- 46 files changed, 138 insertions(+), 64 deletions(-) rename packages/istanbul-report-html-modern/src/{ => lib}/Report.tsx (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/ReportApp.tsx (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/components/CoverageDetail.tsx (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/components/LineNumbers.tsx (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/components/SummaryHeader.tsx (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/components/SummaryList.tsx (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/components/SummaryTree.tsx (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/components/TopControl.tsx (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/css.d.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/hash-route.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/helpers/annotate.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/helpers/build-report-files.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/helpers/color.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/helpers/derive-views.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/helpers/empty-coverage.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/helpers/line-hits.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/helpers/summary.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/icons/TreeViewIcon.tsx (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/index.css (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/index.ts (87%) rename packages/istanbul-report-html-modern/src/{ => lib}/monaco-esm.d.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/monaco.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/paths.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/theme-context.tsx (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/types.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/useHashPath.ts (100%) rename packages/istanbul-report-html-modern/src/{ => lib}/vite-env.d.ts (100%) rename packages/istanbul-report-html-modern/src/{ => page}/components/ReportFooter.css (100%) rename packages/istanbul-report-html-modern/src/{ => page}/components/ReportFooter.tsx (100%) rename packages/istanbul-report-html-modern/src/{helpers => page}/decompress.ts (100%) rename packages/istanbul-report-html-modern/{ => src/page}/index.html (92%) rename packages/istanbul-report-html-modern/src/{helpers/loadData.ts => page/load-data.ts} (53%) rename packages/istanbul-report-html-modern/src/{ => page}/main.tsx (58%) rename packages/istanbul-report-html-modern/src/{ => page}/report-data.ts (100%) rename packages/istanbul-report-html-modern/src/{ReportPage.tsx => page/shell.tsx} (64%) create mode 100644 packages/istanbul-report-html-modern/src/page/vite-env.d.ts diff --git a/packages/istanbul-lib-report/src/reports/html-modern/write-report.ts b/packages/istanbul-lib-report/src/reports/html-modern/write-report.ts index 002cac8..d248779 100644 --- a/packages/istanbul-lib-report/src/reports/html-modern/write-report.ts +++ b/packages/istanbul-lib-report/src/reports/html-modern/write-report.ts @@ -14,8 +14,9 @@ export function resolveReportHtmlDist(): string | null { try { const packageJsonPath = require.resolve("@vitest/istanbul-report-html-modern/package.json"); const distDir = path.join(path.dirname(packageJsonPath), "dist"); - if (fs.existsSync(path.join(distDir, "index.html"))) { - return distDir; + const pageDir = path.join(distDir, "page"); + if (fs.existsSync(path.join(pageDir, "index.html"))) { + return pageDir; } } catch { // @vitest/istanbul-report-html-modern is not installed or dist is missing diff --git a/packages/istanbul-report-html-modern/README.md b/packages/istanbul-report-html-modern/README.md index 30e4bf3..f00117a 100644 --- a/packages/istanbul-report-html-modern/README.md +++ b/packages/istanbul-report-html-modern/README.md @@ -2,13 +2,23 @@ Modern Istanbul HTML coverage report: -- **React components** — `import { ReportApp } from "@vitest/istanbul-report-html-modern"` + `import "@vitest/istanbul-report-html-modern/style.css"` -- **Single-file HTML** — `dist/index.html` (used by `@vitest/istanbul-lib-report`’s `html-modern` report) +- **React library** — `import { ReportApp } from "@vitest/istanbul-report-html-modern"` + `import "@vitest/istanbul-report-html-modern/style.css"` +- **Single-file HTML page** — `dist/page/index.html` (used by `@vitest/istanbul-lib-report`’s `html-modern` report) + +## Layout + +``` +src/ + lib/ # component library (tsdown → dist/index.js) + page/ # single-file HTML shell (vite → dist/page/index.html) +``` + +The page build imports only the public library API, not lib internals. ## Scripts ```bash -pnpm build # dist/index.js + style.css + index.html -pnpm dev # full report page with mock data -pnpm play # UI playground +pnpm build # dist/index.js + style.css, then dist/page/index.html +pnpm dev # full report page; reads coverage/report-data.json (generate via pnpm test) +pnpm play # UI playground (library API) ``` diff --git a/packages/istanbul-report-html-modern/playground/src/App.tsx b/packages/istanbul-report-html-modern/playground/src/App.tsx index 68a3802..360fe4a 100644 --- a/packages/istanbul-report-html-modern/playground/src/App.tsx +++ b/packages/istanbul-report-html-modern/playground/src/App.tsx @@ -1,9 +1,9 @@ import reportData from "@repo/fixtures/report-data.json"; -import { useMemo } from "react"; +import { buildReportFiles, ReportApp } from "@vitest/istanbul-report-html-modern"; -import { buildReportFiles } from "../../src/helpers/build-report-files"; -import { ReportApp } from "../../src/ReportApp"; -import type { FileCoverageData } from "../../src/types"; +import "@vitest/istanbul-report-html-modern/style.css"; +import type { FileCoverageData } from "@vitest/istanbul-report-html-modern"; +import { useMemo } from "react"; export function App() { const prepared = useMemo( diff --git a/packages/istanbul-report-html-modern/src/Report.tsx b/packages/istanbul-report-html-modern/src/lib/Report.tsx similarity index 100% rename from packages/istanbul-report-html-modern/src/Report.tsx rename to packages/istanbul-report-html-modern/src/lib/Report.tsx diff --git a/packages/istanbul-report-html-modern/src/ReportApp.tsx b/packages/istanbul-report-html-modern/src/lib/ReportApp.tsx similarity index 100% rename from packages/istanbul-report-html-modern/src/ReportApp.tsx rename to packages/istanbul-report-html-modern/src/lib/ReportApp.tsx diff --git a/packages/istanbul-report-html-modern/src/components/CoverageDetail.tsx b/packages/istanbul-report-html-modern/src/lib/components/CoverageDetail.tsx similarity index 100% rename from packages/istanbul-report-html-modern/src/components/CoverageDetail.tsx rename to packages/istanbul-report-html-modern/src/lib/components/CoverageDetail.tsx diff --git a/packages/istanbul-report-html-modern/src/components/LineNumbers.tsx b/packages/istanbul-report-html-modern/src/lib/components/LineNumbers.tsx similarity index 100% rename from packages/istanbul-report-html-modern/src/components/LineNumbers.tsx rename to packages/istanbul-report-html-modern/src/lib/components/LineNumbers.tsx diff --git a/packages/istanbul-report-html-modern/src/components/SummaryHeader.tsx b/packages/istanbul-report-html-modern/src/lib/components/SummaryHeader.tsx similarity index 100% rename from packages/istanbul-report-html-modern/src/components/SummaryHeader.tsx rename to packages/istanbul-report-html-modern/src/lib/components/SummaryHeader.tsx diff --git a/packages/istanbul-report-html-modern/src/components/SummaryList.tsx b/packages/istanbul-report-html-modern/src/lib/components/SummaryList.tsx similarity index 100% rename from packages/istanbul-report-html-modern/src/components/SummaryList.tsx rename to packages/istanbul-report-html-modern/src/lib/components/SummaryList.tsx diff --git a/packages/istanbul-report-html-modern/src/components/SummaryTree.tsx b/packages/istanbul-report-html-modern/src/lib/components/SummaryTree.tsx similarity index 100% rename from packages/istanbul-report-html-modern/src/components/SummaryTree.tsx rename to packages/istanbul-report-html-modern/src/lib/components/SummaryTree.tsx diff --git a/packages/istanbul-report-html-modern/src/components/TopControl.tsx b/packages/istanbul-report-html-modern/src/lib/components/TopControl.tsx similarity index 100% rename from packages/istanbul-report-html-modern/src/components/TopControl.tsx rename to packages/istanbul-report-html-modern/src/lib/components/TopControl.tsx diff --git a/packages/istanbul-report-html-modern/src/css.d.ts b/packages/istanbul-report-html-modern/src/lib/css.d.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/css.d.ts rename to packages/istanbul-report-html-modern/src/lib/css.d.ts diff --git a/packages/istanbul-report-html-modern/src/hash-route.ts b/packages/istanbul-report-html-modern/src/lib/hash-route.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/hash-route.ts rename to packages/istanbul-report-html-modern/src/lib/hash-route.ts diff --git a/packages/istanbul-report-html-modern/src/helpers/annotate.ts b/packages/istanbul-report-html-modern/src/lib/helpers/annotate.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/helpers/annotate.ts rename to packages/istanbul-report-html-modern/src/lib/helpers/annotate.ts diff --git a/packages/istanbul-report-html-modern/src/helpers/build-report-files.ts b/packages/istanbul-report-html-modern/src/lib/helpers/build-report-files.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/helpers/build-report-files.ts rename to packages/istanbul-report-html-modern/src/lib/helpers/build-report-files.ts diff --git a/packages/istanbul-report-html-modern/src/helpers/color.ts b/packages/istanbul-report-html-modern/src/lib/helpers/color.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/helpers/color.ts rename to packages/istanbul-report-html-modern/src/lib/helpers/color.ts diff --git a/packages/istanbul-report-html-modern/src/helpers/derive-views.ts b/packages/istanbul-report-html-modern/src/lib/helpers/derive-views.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/helpers/derive-views.ts rename to packages/istanbul-report-html-modern/src/lib/helpers/derive-views.ts diff --git a/packages/istanbul-report-html-modern/src/helpers/empty-coverage.ts b/packages/istanbul-report-html-modern/src/lib/helpers/empty-coverage.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/helpers/empty-coverage.ts rename to packages/istanbul-report-html-modern/src/lib/helpers/empty-coverage.ts diff --git a/packages/istanbul-report-html-modern/src/helpers/line-hits.ts b/packages/istanbul-report-html-modern/src/lib/helpers/line-hits.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/helpers/line-hits.ts rename to packages/istanbul-report-html-modern/src/lib/helpers/line-hits.ts diff --git a/packages/istanbul-report-html-modern/src/helpers/summary.ts b/packages/istanbul-report-html-modern/src/lib/helpers/summary.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/helpers/summary.ts rename to packages/istanbul-report-html-modern/src/lib/helpers/summary.ts diff --git a/packages/istanbul-report-html-modern/src/icons/TreeViewIcon.tsx b/packages/istanbul-report-html-modern/src/lib/icons/TreeViewIcon.tsx similarity index 100% rename from packages/istanbul-report-html-modern/src/icons/TreeViewIcon.tsx rename to packages/istanbul-report-html-modern/src/lib/icons/TreeViewIcon.tsx diff --git a/packages/istanbul-report-html-modern/src/index.css b/packages/istanbul-report-html-modern/src/lib/index.css similarity index 100% rename from packages/istanbul-report-html-modern/src/index.css rename to packages/istanbul-report-html-modern/src/lib/index.css diff --git a/packages/istanbul-report-html-modern/src/index.ts b/packages/istanbul-report-html-modern/src/lib/index.ts similarity index 87% rename from packages/istanbul-report-html-modern/src/index.ts rename to packages/istanbul-report-html-modern/src/lib/index.ts index ec9de9f..e574849 100644 --- a/packages/istanbul-report-html-modern/src/index.ts +++ b/packages/istanbul-report-html-modern/src/lib/index.ts @@ -3,6 +3,7 @@ import "./index.css"; export { Report } from "./Report"; export { ReportApp } from "./ReportApp"; export { buildReportFiles } from "./helpers/build-report-files"; +export type { ReportDataLike } from "./helpers/build-report-files"; export { filesToDataSource, buildSummaryTree } from "./helpers/summary"; export { projectRootBaseName, resolveSource, toAbsolutePath, toRelativePath } from "./paths"; export type { diff --git a/packages/istanbul-report-html-modern/src/monaco-esm.d.ts b/packages/istanbul-report-html-modern/src/lib/monaco-esm.d.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/monaco-esm.d.ts rename to packages/istanbul-report-html-modern/src/lib/monaco-esm.d.ts diff --git a/packages/istanbul-report-html-modern/src/monaco.ts b/packages/istanbul-report-html-modern/src/lib/monaco.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/monaco.ts rename to packages/istanbul-report-html-modern/src/lib/monaco.ts diff --git a/packages/istanbul-report-html-modern/src/paths.ts b/packages/istanbul-report-html-modern/src/lib/paths.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/paths.ts rename to packages/istanbul-report-html-modern/src/lib/paths.ts diff --git a/packages/istanbul-report-html-modern/src/theme-context.tsx b/packages/istanbul-report-html-modern/src/lib/theme-context.tsx similarity index 100% rename from packages/istanbul-report-html-modern/src/theme-context.tsx rename to packages/istanbul-report-html-modern/src/lib/theme-context.tsx diff --git a/packages/istanbul-report-html-modern/src/types.ts b/packages/istanbul-report-html-modern/src/lib/types.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/types.ts rename to packages/istanbul-report-html-modern/src/lib/types.ts diff --git a/packages/istanbul-report-html-modern/src/useHashPath.ts b/packages/istanbul-report-html-modern/src/lib/useHashPath.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/useHashPath.ts rename to packages/istanbul-report-html-modern/src/lib/useHashPath.ts diff --git a/packages/istanbul-report-html-modern/src/vite-env.d.ts b/packages/istanbul-report-html-modern/src/lib/vite-env.d.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/vite-env.d.ts rename to packages/istanbul-report-html-modern/src/lib/vite-env.d.ts diff --git a/packages/istanbul-report-html-modern/src/components/ReportFooter.css b/packages/istanbul-report-html-modern/src/page/components/ReportFooter.css similarity index 100% rename from packages/istanbul-report-html-modern/src/components/ReportFooter.css rename to packages/istanbul-report-html-modern/src/page/components/ReportFooter.css diff --git a/packages/istanbul-report-html-modern/src/components/ReportFooter.tsx b/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx similarity index 100% rename from packages/istanbul-report-html-modern/src/components/ReportFooter.tsx rename to packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx diff --git a/packages/istanbul-report-html-modern/src/helpers/decompress.ts b/packages/istanbul-report-html-modern/src/page/decompress.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/helpers/decompress.ts rename to packages/istanbul-report-html-modern/src/page/decompress.ts diff --git a/packages/istanbul-report-html-modern/index.html b/packages/istanbul-report-html-modern/src/page/index.html similarity index 92% rename from packages/istanbul-report-html-modern/index.html rename to packages/istanbul-report-html-modern/src/page/index.html index 38a932c..78c7f7d 100644 --- a/packages/istanbul-report-html-modern/index.html +++ b/packages/istanbul-report-html-modern/src/page/index.html @@ -21,6 +21,6 @@ - + diff --git a/packages/istanbul-report-html-modern/src/helpers/loadData.ts b/packages/istanbul-report-html-modern/src/page/load-data.ts similarity index 53% rename from packages/istanbul-report-html-modern/src/helpers/loadData.ts rename to packages/istanbul-report-html-modern/src/page/load-data.ts index 4d5e784..023c7a9 100644 --- a/packages/istanbul-report-html-modern/src/helpers/loadData.ts +++ b/packages/istanbul-report-html-modern/src/page/load-data.ts @@ -1,9 +1,9 @@ -import type { ReportData } from "../report-data.ts"; import { base64ToUint8Array, decompressGzip } from "./decompress.ts"; +import type { ReportData } from "./report-data.ts"; /** * Decode gzip+base64 `window.reportData` (as embedded by the html-modern reporter) - * into a plain object. No-op when data is already an object (e.g. local Vite dev). + * into a plain object. No-op when data is already an object. */ export async function loadReportData(): Promise { const embeddedReportData = window.reportData as ReportData | string | undefined; @@ -15,3 +15,14 @@ export async function loadReportData(): Promise { const decompressedText = await decompressGzip(base64ToUint8Array(embeddedReportData)); window.reportData = JSON.parse(decompressedText) as ReportData; } + +/** Load mock report data in Vite dev (same source as the playground). */ +export async function loadDevReportData(): Promise { + if (import.meta.env.DEV) { + const { default: reportData } = await import("@repo/fixtures/report-data.json"); + window.reportData = reportData as ReportData; + return; + } + + await loadReportData(); +} diff --git a/packages/istanbul-report-html-modern/src/main.tsx b/packages/istanbul-report-html-modern/src/page/main.tsx similarity index 58% rename from packages/istanbul-report-html-modern/src/main.tsx rename to packages/istanbul-report-html-modern/src/page/main.tsx index 2369613..754b78c 100644 --- a/packages/istanbul-report-html-modern/src/main.tsx +++ b/packages/istanbul-report-html-modern/src/page/main.tsx @@ -2,13 +2,13 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import "monaco-editor-css"; -import { loadReportData } from "./helpers/loadData.ts"; -import ReportPage from "./ReportPage.tsx"; +import { loadDevReportData } from "./load-data.ts"; +import ReportShell from "./shell.tsx"; -await loadReportData(); +await loadDevReportData(); createRoot(document.getElementById("root")!).render( - + , ); diff --git a/packages/istanbul-report-html-modern/src/report-data.ts b/packages/istanbul-report-html-modern/src/page/report-data.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/report-data.ts rename to packages/istanbul-report-html-modern/src/page/report-data.ts diff --git a/packages/istanbul-report-html-modern/src/ReportPage.tsx b/packages/istanbul-report-html-modern/src/page/shell.tsx similarity index 64% rename from packages/istanbul-report-html-modern/src/ReportPage.tsx rename to packages/istanbul-report-html-modern/src/page/shell.tsx index f368d64..55010de 100644 --- a/packages/istanbul-report-html-modern/src/ReportPage.tsx +++ b/packages/istanbul-report-html-modern/src/page/shell.tsx @@ -1,26 +1,23 @@ +import { buildReportFiles, ReportApp } from "@vitest/istanbul-report-html-modern"; + +import "@vitest/istanbul-report-html-modern/style.css"; import { useMemo } from "react"; import { ReportFooter } from "./components/ReportFooter"; -import { buildReportFiles } from "./helpers/build-report-files"; -import { ReportApp } from "./ReportApp"; - -import "./index.css"; +import type { ReportData } from "./report-data"; /** Standalone single-file HTML page shell around {@link ReportApp}. */ -export function ReportPage() { - const reportData = window.reportData; +export function ReportShell() { + const reportData = window.reportData as ReportData | undefined | null; const prepared = useMemo(() => { - if (reportData === undefined) { + if (reportData == null) { return null; } return buildReportFiles(reportData); }, [reportData]); - console.log({ - prepared, - reportData, - }); - if (prepared === null || reportData === undefined) { + + if (prepared === null || reportData == null) { return

No report data loaded.

; } @@ -38,4 +35,4 @@ export function ReportPage() { ); } -export default ReportPage; +export default ReportShell; diff --git a/packages/istanbul-report-html-modern/src/page/vite-env.d.ts b/packages/istanbul-report-html-modern/src/page/vite-env.d.ts new file mode 100644 index 0000000..7161735 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/page/vite-env.d.ts @@ -0,0 +1,10 @@ +/// + +declare module "monaco-editor-css" {} + +declare module "@repo/fixtures/report-data.json" { + import type { ReportData } from "./report-data.ts"; + + const reportData: ReportData; + export default reportData; +} diff --git a/packages/istanbul-report-html-modern/test/paths.test.ts b/packages/istanbul-report-html-modern/test/paths.test.ts index 2175e50..6c04fe5 100644 --- a/packages/istanbul-report-html-modern/test/paths.test.ts +++ b/packages/istanbul-report-html-modern/test/paths.test.ts @@ -1,6 +1,11 @@ import { assert, describe, it } from "vitest"; -import { projectRootBaseName, resolveSource, toAbsolutePath, toRelativePath } from "../src/paths"; +import { + projectRootBaseName, + resolveSource, + toAbsolutePath, + toRelativePath, +} from "../src/lib/paths"; describe("toRelativePath", () => { it("strips the projectRoot prefix", () => { diff --git a/packages/istanbul-report-html-modern/test/summary.test.ts b/packages/istanbul-report-html-modern/test/summary.test.ts index 3ed948b..7aafac2 100644 --- a/packages/istanbul-report-html-modern/test/summary.test.ts +++ b/packages/istanbul-report-html-modern/test/summary.test.ts @@ -1,15 +1,15 @@ import { assert, describe, it } from "vitest"; -import { buildReportFiles } from "../src/helpers/build-report-files"; -import { deriveSummaryViews } from "../src/helpers/derive-views"; -import { computeLineHits } from "../src/helpers/line-hits"; +import { buildReportFiles } from "../src/lib/helpers/build-report-files"; +import { deriveSummaryViews } from "../src/lib/helpers/derive-views"; +import { computeLineHits } from "../src/lib/helpers/line-hits"; import { buildSummaryTree, fileCoverageToSummary, filesToDataSource, percent, -} from "../src/helpers/summary"; -import type { FileCoverageData } from "../src/types"; +} from "../src/lib/helpers/summary"; +import type { FileCoverageData } from "../src/lib/types"; function makeFile(path: string, statementHits: number[]): FileCoverageData { const statementMap: FileCoverageData["statementMap"] = {}; diff --git a/packages/istanbul-report-html-modern/tsconfig.json b/packages/istanbul-report-html-modern/tsconfig.json index b4ea77c..df36b70 100644 --- a/packages/istanbul-report-html-modern/tsconfig.json +++ b/packages/istanbul-report-html-modern/tsconfig.json @@ -8,7 +8,8 @@ "noEmit": true }, "include": [ - "src", + "src/lib", + "src/page", "vite.config.ts", "vite.config.playground.ts", "vite-plugin-report-data-dev.ts", diff --git a/packages/istanbul-report-html-modern/tsdown.config.ts b/packages/istanbul-report-html-modern/tsdown.config.ts index e2e9296..d04633f 100644 --- a/packages/istanbul-report-html-modern/tsdown.config.ts +++ b/packages/istanbul-report-html-modern/tsdown.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "tsdown"; /** React component library → `dist/index.js` + `dist/style.css` */ export default defineConfig({ - entry: ["src/index.ts"], + entry: ["src/lib/index.ts"], platform: "neutral", dts: true, exports: true, diff --git a/packages/istanbul-report-html-modern/vite-plugin-report-data-dev.ts b/packages/istanbul-report-html-modern/vite-plugin-report-data-dev.ts index 14c34b0..66eebd2 100644 --- a/packages/istanbul-report-html-modern/vite-plugin-report-data-dev.ts +++ b/packages/istanbul-report-html-modern/vite-plugin-report-data-dev.ts @@ -10,22 +10,23 @@ const packageRoot = dirname(fileURLToPath(import.meta.url)); const repoRoot = join(packageRoot, "../.."); const mockPath = join(repoRoot, "coverage/report-data.json"); -function readDevReportData(): string { - if (!fs.existsSync(mockPath)) { - return "null"; - } - - const reportData = JSON.parse(fs.readFileSync(mockPath, "utf-8")) as unknown; - return JSON.stringify(reportData); -} - -/** Injects mock coverage data from the repo-root coverage/report-data.json during dev. */ +/** In dev, neutralize the production placeholder so inline HTML stays valid JS. */ export function reportDataDevPlugin(): Plugin { return { name: "report-data-dev", apply: "serve", transformIndexHtml(html) { - return html.replace(REPORT_DATA_PLACEHOLDER, readDevReportData()); + if (!html.includes(REPORT_DATA_PLACEHOLDER)) { + return html; + } + + if (!fs.existsSync(mockPath)) { + console.warn( + `[report-data-dev] ${mockPath} not found; run repo tests once to generate it, or use pnpm play`, + ); + } + + return html.replace(REPORT_DATA_PLACEHOLDER, "undefined"); }, }; } diff --git a/packages/istanbul-report-html-modern/vite.config.playground.ts b/packages/istanbul-report-html-modern/vite.config.playground.ts index 57353f2..7215d11 100644 --- a/packages/istanbul-report-html-modern/vite.config.playground.ts +++ b/packages/istanbul-report-html-modern/vite.config.playground.ts @@ -14,15 +14,29 @@ const monacoCss = join( "../../min/vs/editor/editor.main.css", ); -/** UI playground (consumes React components from `src/`) */ +/** UI playground (consumes the public library API). */ export default defineConfig({ root: join(packageRoot, "playground"), plugins: [react()], resolve: { - alias: { - "@repo/fixtures": join(repoRoot, "coverage"), - "monaco-editor-css": monacoCss, - }, + alias: [ + { + find: "@vitest/istanbul-report-html-modern/style.css", + replacement: join(packageRoot, "src/lib/index.css"), + }, + { + find: "@vitest/istanbul-report-html-modern", + replacement: join(packageRoot, "src/lib/index.ts"), + }, + { + find: "@repo/fixtures", + replacement: join(repoRoot, "coverage"), + }, + { + find: "monaco-editor-css", + replacement: monacoCss, + }, + ], }, optimizeDeps: { include: [ diff --git a/packages/istanbul-report-html-modern/vite.config.ts b/packages/istanbul-report-html-modern/vite.config.ts index 9f61ca4..3dffd3c 100644 --- a/packages/istanbul-report-html-modern/vite.config.ts +++ b/packages/istanbul-report-html-modern/vite.config.ts @@ -10,25 +10,48 @@ import { reportDataDevPlugin } from "./vite-plugin-report-data-dev.js"; const require = createRequire(import.meta.url); const packageRoot = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(packageRoot, "../.."); const monacoCss = join( dirname(require.resolve("monaco-editor")), "../../min/vs/editor/editor.main.css", ); -/** Single-file HTML report page → `dist/index.html` */ -export default defineConfig({ - root: packageRoot, +function libAliases(mode: string) { + const production = mode === "production"; + return [ + { + find: "@vitest/istanbul-report-html-modern/style.css", + replacement: join(packageRoot, production ? "dist/style.css" : "src/lib/index.css"), + }, + { + find: "@vitest/istanbul-report-html-modern", + replacement: join(packageRoot, production ? "dist/index.js" : "src/lib/index.ts"), + }, + ] as const; +} + +/** Single-file HTML report page → `dist/page/index.html` */ +export default defineConfig(({ mode }) => ({ + root: join(packageRoot, "src/page"), plugins: [react(), reportDataDevPlugin(), viteSingleFile()], resolve: { - alias: { - "monaco-editor-css": monacoCss, - }, + alias: [ + ...libAliases(mode), + { + find: "@repo/fixtures", + replacement: join(repoRoot, "coverage"), + }, + { + find: "monaco-editor-css", + replacement: monacoCss, + }, + ], }, build: { - outDir: join(packageRoot, "dist"), - emptyOutDir: false, + outDir: join(packageRoot, "dist/page"), + emptyOutDir: true, }, server: { port: 51025, }, -}); +})); From d74c4e0d1199508b79a5f7bfd890bb456795af8c Mon Sep 17 00:00:00 2001 From: Travis Zhang Date: Wed, 2 Sep 2026 16:37:15 +0800 Subject: [PATCH 09/15] refactor: remove unused CSS and HTML language registrations from Monaco editor --- .../istanbul-report-html-modern/src/lib/monaco-esm.d.ts | 2 -- packages/istanbul-report-html-modern/src/lib/monaco.ts | 9 +-------- .../vite.config.playground.ts | 2 -- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/istanbul-report-html-modern/src/lib/monaco-esm.d.ts b/packages/istanbul-report-html-modern/src/lib/monaco-esm.d.ts index 42af2b9..dd0045a 100644 --- a/packages/istanbul-report-html-modern/src/lib/monaco-esm.d.ts +++ b/packages/istanbul-report-html-modern/src/lib/monaco-esm.d.ts @@ -5,5 +5,3 @@ declare module "monaco-editor/editor/editor.api" { declare module "monaco-editor/editor/contrib/hover/browser/hoverContribution" {} declare module "monaco-editor/languages/definitions/javascript/register" {} declare module "monaco-editor/languages/definitions/typescript/register" {} -declare module "monaco-editor/languages/definitions/css/register" {} -declare module "monaco-editor/languages/definitions/html/register" {} diff --git a/packages/istanbul-report-html-modern/src/lib/monaco.ts b/packages/istanbul-report-html-modern/src/lib/monaco.ts index 353af19..8be76e9 100644 --- a/packages/istanbul-report-html-modern/src/lib/monaco.ts +++ b/packages/istanbul-report-html-modern/src/lib/monaco.ts @@ -1,12 +1,11 @@ import * as monaco from "monaco-editor/editor/editor.api"; import "monaco-editor/editor/contrib/hover/browser/hoverContribution"; -import "monaco-editor/languages/definitions/css/register"; -import "monaco-editor/languages/definitions/html/register"; import "monaco-editor/languages/definitions/javascript/register"; import "monaco-editor/languages/definitions/typescript/register"; export { monaco }; +/** Map coverage file paths to Monaco languages (JS/TS only; others use plaintext). */ export function languageFromPath(filePath: string): string { const ext = filePath.split(".").pop()?.toLowerCase(); switch (ext) { @@ -20,12 +19,6 @@ export function languageFromPath(filePath: string): string { case "cjs": case "jsx": return "javascript"; - case "css": - case "scss": - return "css"; - case "html": - case "htm": - return "html"; default: return "plaintext"; } diff --git a/packages/istanbul-report-html-modern/vite.config.playground.ts b/packages/istanbul-report-html-modern/vite.config.playground.ts index 7215d11..c6b4f5a 100644 --- a/packages/istanbul-report-html-modern/vite.config.playground.ts +++ b/packages/istanbul-report-html-modern/vite.config.playground.ts @@ -44,8 +44,6 @@ export default defineConfig({ "antd", "monaco-editor/editor/editor.api", "monaco-editor/editor/contrib/hover/browser/hoverContribution", - "monaco-editor/languages/definitions/css/register", - "monaco-editor/languages/definitions/html/register", "monaco-editor/languages/definitions/javascript/register", "monaco-editor/languages/definitions/typescript/register", "react-dom/server", From 0e27a45f8be604766eb24130de8f31498ded6945 Mon Sep 17 00:00:00 2001 From: Travis Zhang Date: Thu, 3 Sep 2026 10:45:29 +0800 Subject: [PATCH 10/15] refactor: update typecheck script and exclude modern HTML report from TypeScript compilation --- package.json | 2 +- tsconfig.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index af42a81..8ec9940 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "test": "vitest", "fmt": "oxfmt", "lint": "oxlint", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit && pnpm -r --if-present run typecheck" }, "devDependencies": { "@e18e/eslint-plugin": "catalog:", diff --git a/tsconfig.json b/tsconfig.json index 0f31ac4..868958a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,5 +17,6 @@ "@vitest/istanbul-lib-source-maps": ["./packages/istanbul-lib-source-maps/src/index.ts"] } }, - "include": ["packages/*/src", "packages/*/test"] + "include": ["packages/*/src", "packages/*/test"], + "exclude": ["packages/istanbul-report-html-modern"] } From f4dd6c53b041ec86cc2d55b709f1fa399d9d0c8c Mon Sep 17 00:00:00 2001 From: Travis Zhang Date: Tue, 15 Sep 2026 11:09:38 +0800 Subject: [PATCH 11/15] feat(html-modern): add fileTags filtering and migrate UI to Preact/CodeMirror Support glob-based file tags in the report payload and TagFilter UI, flatten src/lib into src, and replace React/Monaco with Preact/CodeMirror. --- html-modern.cjs | 2 - packages/istanbul-lib-report/package.json | 4 +- packages/istanbul-lib-report/src/index.ts | 2 + .../reports/html-modern/coverage-report.ts | 25 +- .../src/reports/html-modern/file-tags.ts | 25 + .../src/reports/html-modern/index.ts | 3 +- .../src/reports/html-modern/options.ts | 5 + .../src/reports/html-modern/paths.ts | 30 + .../reports/html-modern/resolve-file-tags.ts | 44 + .../src/reports/html-modern/types.ts | 6 +- .../src/reports/html-modern/write-report.ts | 2 +- .../html-modern/resolve-file-tags.test.ts | 49 + .../istanbul-report-html-modern/.gitignore | 4 +- .../istanbul-report-html-modern/README.md | 24 - .../istanbul-report-html-modern/package.json | 50 +- .../playground/src/App.tsx | 44 +- .../playground/src/index.tsx | 12 +- .../playground/src/vite-env.d.ts | 17 +- .../report-data-placeholder.ts | 2 +- .../src/Report.tsx | 153 ++ .../src/{lib => }/ReportApp.tsx | 26 +- .../src/codemirror.ts | 79 + .../src/components/CoverageDetail.tsx | 174 ++ .../src/components/CoverageMeter.tsx | 23 + .../src/components/LineNumbers.tsx | 75 + .../src/components/SummaryHeader.tsx | 93 + .../src/components/SummaryList.tsx | 98 + .../src/components/SummaryTree.tsx | 107 ++ .../src/components/TagFilter.tsx | 107 ++ .../src/components/TopControl.tsx | 86 + .../src/components/table-utils.tsx | 76 + .../src/{lib => }/css.d.ts | 0 .../src/file-tags.ts | 25 + .../src/{lib => }/hash-route.ts | 0 .../src/{lib => }/helpers/annotate.ts | 0 .../{lib => }/helpers/build-report-files.ts | 16 + .../src/helpers/color.ts | 25 + .../src/{lib => }/helpers/derive-views.ts | 33 +- .../src/{lib => }/helpers/empty-coverage.ts | 0 .../src/{lib => }/helpers/line-hits.ts | 0 .../src/{lib => }/helpers/summary.ts | 0 .../istanbul-report-html-modern/src/index.css | 699 +++++++ .../src/{lib => }/index.ts | 2 + .../src/lib/Report.tsx | 125 -- .../src/lib/components/CoverageDetail.tsx | 149 -- .../src/lib/components/LineNumbers.tsx | 57 - .../src/lib/components/SummaryHeader.tsx | 120 -- .../src/lib/components/SummaryList.tsx | 95 - .../src/lib/components/SummaryTree.tsx | 100 - .../src/lib/components/TopControl.tsx | 79 - .../src/lib/helpers/color.ts | 9 - .../src/lib/icons/TreeViewIcon.tsx | 18 - .../src/lib/index.css | 147 -- .../src/lib/monaco-esm.d.ts | 7 - .../src/lib/monaco.ts | 25 - .../src/lib/vite-env.d.ts | 3 - .../src/page/components/ReportFooter.tsx | 2 +- .../src/page/decompress.ts | 1 - .../src/page/load-data.ts | 4 +- .../src/page/main.tsx | 14 +- .../src/page/report-data.ts | 11 +- .../src/page/shell.tsx | 11 +- .../src/page/vite-env.d.ts | 4 +- .../src/{lib => }/paths.ts | 0 .../src/{lib => }/theme-context.tsx | 14 +- .../src/{lib => }/types.ts | 13 + .../src/{lib => }/useHashPath.ts | 2 +- .../src/vite-env.d.ts | 1 + .../test/color.test.ts | 21 + .../test/paths.test.ts | 7 +- .../test/summary.test.ts | 34 +- .../istanbul-report-html-modern/tsconfig.json | 29 +- .../tsdown.config.ts | 4 +- .../vite-plugin-report-data-dev.ts | 5 +- .../vite.config.playground.ts | 34 +- .../vite.config.ts | 21 +- .../vitest.config.ts | 33 +- pnpm-lock.yaml | 1670 +++++++---------- pnpm-workspace.yaml | 22 +- tsconfig.json | 3 +- vitest.config.ts | 2 +- 81 files changed, 2975 insertions(+), 2168 deletions(-) delete mode 100644 html-modern.cjs create mode 100644 packages/istanbul-lib-report/src/reports/html-modern/file-tags.ts create mode 100644 packages/istanbul-lib-report/src/reports/html-modern/paths.ts create mode 100644 packages/istanbul-lib-report/src/reports/html-modern/resolve-file-tags.ts create mode 100644 packages/istanbul-lib-report/test/reports/html-modern/resolve-file-tags.test.ts delete mode 100644 packages/istanbul-report-html-modern/README.md create mode 100644 packages/istanbul-report-html-modern/src/Report.tsx rename packages/istanbul-report-html-modern/src/{lib => }/ReportApp.tsx (65%) create mode 100644 packages/istanbul-report-html-modern/src/codemirror.ts create mode 100644 packages/istanbul-report-html-modern/src/components/CoverageDetail.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/CoverageMeter.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/LineNumbers.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/SummaryHeader.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/SummaryList.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/SummaryTree.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/TagFilter.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/TopControl.tsx create mode 100644 packages/istanbul-report-html-modern/src/components/table-utils.tsx rename packages/istanbul-report-html-modern/src/{lib => }/css.d.ts (100%) create mode 100644 packages/istanbul-report-html-modern/src/file-tags.ts rename packages/istanbul-report-html-modern/src/{lib => }/hash-route.ts (100%) rename packages/istanbul-report-html-modern/src/{lib => }/helpers/annotate.ts (100%) rename packages/istanbul-report-html-modern/src/{lib => }/helpers/build-report-files.ts (62%) create mode 100644 packages/istanbul-report-html-modern/src/helpers/color.ts rename packages/istanbul-report-html-modern/src/{lib => }/helpers/derive-views.ts (60%) rename packages/istanbul-report-html-modern/src/{lib => }/helpers/empty-coverage.ts (100%) rename packages/istanbul-report-html-modern/src/{lib => }/helpers/line-hits.ts (100%) rename packages/istanbul-report-html-modern/src/{lib => }/helpers/summary.ts (100%) create mode 100644 packages/istanbul-report-html-modern/src/index.css rename packages/istanbul-report-html-modern/src/{lib => }/index.ts (84%) delete mode 100644 packages/istanbul-report-html-modern/src/lib/Report.tsx delete mode 100644 packages/istanbul-report-html-modern/src/lib/components/CoverageDetail.tsx delete mode 100644 packages/istanbul-report-html-modern/src/lib/components/LineNumbers.tsx delete mode 100644 packages/istanbul-report-html-modern/src/lib/components/SummaryHeader.tsx delete mode 100644 packages/istanbul-report-html-modern/src/lib/components/SummaryList.tsx delete mode 100644 packages/istanbul-report-html-modern/src/lib/components/SummaryTree.tsx delete mode 100644 packages/istanbul-report-html-modern/src/lib/components/TopControl.tsx delete mode 100644 packages/istanbul-report-html-modern/src/lib/helpers/color.ts delete mode 100644 packages/istanbul-report-html-modern/src/lib/icons/TreeViewIcon.tsx delete mode 100644 packages/istanbul-report-html-modern/src/lib/index.css delete mode 100644 packages/istanbul-report-html-modern/src/lib/monaco-esm.d.ts delete mode 100644 packages/istanbul-report-html-modern/src/lib/monaco.ts delete mode 100644 packages/istanbul-report-html-modern/src/lib/vite-env.d.ts rename packages/istanbul-report-html-modern/src/{lib => }/paths.ts (100%) rename packages/istanbul-report-html-modern/src/{lib => }/theme-context.tsx (87%) rename packages/istanbul-report-html-modern/src/{lib => }/types.ts (73%) rename packages/istanbul-report-html-modern/src/{lib => }/useHashPath.ts (91%) create mode 100644 packages/istanbul-report-html-modern/src/vite-env.d.ts create mode 100644 packages/istanbul-report-html-modern/test/color.test.ts diff --git a/html-modern.cjs b/html-modern.cjs deleted file mode 100644 index 7739aa7..0000000 --- a/html-modern.cjs +++ /dev/null @@ -1,2 +0,0 @@ -module.exports = - require("./packages/istanbul-lib-report/dist/reports/html-modern/index.mjs").default; diff --git a/packages/istanbul-lib-report/package.json b/packages/istanbul-lib-report/package.json index 91aa537..d3aa54c 100644 --- a/packages/istanbul-lib-report/package.json +++ b/packages/istanbul-lib-report/package.json @@ -36,9 +36,11 @@ "test": "pnpm --filter @vitest/istanbuljs-monorepo run test --project @vitest/istanbul-lib-report" }, "dependencies": { - "@vitest/istanbul-lib-coverage": "workspace:*" + "@vitest/istanbul-lib-coverage": "workspace:*", + "picomatch": "catalog:" }, "devDependencies": { + "@types/picomatch": "catalog:", "@vitest/browser-playwright": "catalog:", "@vitest/istanbul-report-html-modern": "workspace:*", "playwright": "catalog:", diff --git a/packages/istanbul-lib-report/src/index.ts b/packages/istanbul-lib-report/src/index.ts index 14946c9..f664711 100644 --- a/packages/istanbul-lib-report/src/index.ts +++ b/packages/istanbul-lib-report/src/index.ts @@ -63,8 +63,10 @@ export type { } from "./reports/html-modern/index"; export { CoverageReport, + collectAvailableTags, extractIstanbulContext, inferProjectRoot, + resolveFileTags, } from "./reports/html-modern/index"; export { default as HtmlModernReport } from "./reports/html-modern/index"; export type { HtmlOptions, LinkMapper } from "./reports/html/index"; diff --git a/packages/istanbul-lib-report/src/reports/html-modern/coverage-report.ts b/packages/istanbul-lib-report/src/reports/html-modern/coverage-report.ts index 85a0ceb..9bb0d64 100644 --- a/packages/istanbul-lib-report/src/reports/html-modern/coverage-report.ts +++ b/packages/istanbul-lib-report/src/reports/html-modern/coverage-report.ts @@ -3,6 +3,8 @@ import { createRequire } from "node:module"; import { resolveProjectRoot } from "./infer-project-root"; import type { HtmlModernOptions } from "./options"; +import { toRelativePath } from "./paths"; +import { resolveFileTags } from "./resolve-file-tags"; import type { CoverageData, GenerateOptions, @@ -14,7 +16,9 @@ import type { import { resolveReportHtmlDist, writeHtmlReport, writeReportDataJson } from "./write-report"; const require = createRequire(import.meta.url); -const { name: packageName, version: packageVersion } = require("../../../package.json") as { +const { name: packageName, version: packageVersion } = require( + require.resolve("@vitest/istanbul-report-html-modern/package.json"), +) as { name: string; version: string; }; @@ -40,6 +44,9 @@ function serializeHtmlOptions(options: HtmlModernOptions): SerializableHtmlModer if (options.writeReportDataJson !== undefined) { html.writeReportDataJson = options.writeReportDataJson; } + if (options.fileTags !== undefined) { + html.fileTags = options.fileTags; + } return html; } @@ -66,6 +73,14 @@ export class CoverageReport { } } + const projectRoot = resolveProjectRoot(Object.keys(coverage), this.htmlOptions.projectRoot); + const relativePaths = Object.keys(coverage).map((filePath) => + toRelativePath(filePath, projectRoot), + ); + const fileTagsByPath = resolveFileTags(relativePaths, this.htmlOptions.fileTags, { + verbose: this.htmlOptions.verbose, + }); + return { html: serializeHtmlOptions(this.htmlOptions), istanbul, @@ -73,9 +88,11 @@ export class CoverageReport { coverageFileCount: Object.keys(coverage).length, sourceFileCount: Object.keys(sources).length, }, - projectRoot: resolveProjectRoot(Object.keys(coverage), this.htmlOptions.projectRoot), + projectRoot, coverage, sources, + fileTagRules: this.htmlOptions.fileTags, + fileTagsByPath: Object.keys(fileTagsByPath).length > 0 ? fileTagsByPath : undefined, generatedAt: new Date().toISOString(), packageName, packageVersion, @@ -99,7 +116,9 @@ export class CoverageReport { console.log(`HTML report written to ${htmlReportPath}`); } } else if (this.htmlOptions.verbose) { - console.log("@vitest/istanbul-report-html-modern not found, skipping HTML report generation"); + console.log( + "@vitest/istanbul-report-html-modern dist/page not found, skipping HTML report generation", + ); } if (this.htmlOptions.writeReportDataJson) { diff --git a/packages/istanbul-lib-report/src/reports/html-modern/file-tags.ts b/packages/istanbul-lib-report/src/reports/html-modern/file-tags.ts new file mode 100644 index 0000000..c4af5a0 --- /dev/null +++ b/packages/istanbul-lib-report/src/reports/html-modern/file-tags.ts @@ -0,0 +1,25 @@ +/** glob rule that assigns a tag to matching project-relative file paths */ +export interface FileTagRule { + /** glob relative to project root, e.g. `src/payments/**` */ + glob: string; + /** display tag, e.g. `payments` or `P0` */ + tag: string; +} + +/** Unique tag names in config order. */ +export function collectAvailableTags(rules: FileTagRule[] | undefined): string[] { + if (rules === undefined || rules.length === 0) { + return []; + } + + const seen = new Set(); + const tags: string[] = []; + for (const rule of rules) { + if (seen.has(rule.tag)) { + continue; + } + seen.add(rule.tag); + tags.push(rule.tag); + } + return tags; +} diff --git a/packages/istanbul-lib-report/src/reports/html-modern/index.ts b/packages/istanbul-lib-report/src/reports/html-modern/index.ts index bbf6c09..715af39 100644 --- a/packages/istanbul-lib-report/src/reports/html-modern/index.ts +++ b/packages/istanbul-lib-report/src/reports/html-modern/index.ts @@ -11,7 +11,8 @@ import { extractIstanbulContext } from "./istanbul-context"; import type { HtmlModernOptions } from "./options"; export type { LinkMapper } from "../html/index"; -export type { HtmlModernOptions } from "./options"; +export type { FileTagRule, HtmlModernOptions } from "./options"; +export { collectAvailableTags, resolveFileTags } from "./resolve-file-tags"; export type { CoverageData, GenerateOptions, diff --git a/packages/istanbul-lib-report/src/reports/html-modern/options.ts b/packages/istanbul-lib-report/src/reports/html-modern/options.ts index 7cfbafa..c4bff34 100644 --- a/packages/istanbul-lib-report/src/reports/html-modern/options.ts +++ b/packages/istanbul-lib-report/src/reports/html-modern/options.ts @@ -1,4 +1,7 @@ import type { LinkMapper } from "../html/index"; +import type { FileTagRule } from "./file-tags"; + +export type { FileTagRule }; /** options accepted by {@link HtmlModernReport} */ export interface HtmlModernOptions { @@ -20,4 +23,6 @@ export interface HtmlModernOptions { projectRoot?: string; /** write `report-data.json` alongside `index.html` in the report output directory */ writeReportDataJson?: boolean; + /** tag rules used to filter files in the HTML report UI */ + fileTags?: FileTagRule[]; } diff --git a/packages/istanbul-lib-report/src/reports/html-modern/paths.ts b/packages/istanbul-lib-report/src/reports/html-modern/paths.ts new file mode 100644 index 0000000..59a953a --- /dev/null +++ b/packages/istanbul-lib-report/src/reports/html-modern/paths.ts @@ -0,0 +1,30 @@ +/** POSIX-ify slashes; keep a leading `/` so absolute Unix paths stay absolute. */ +export function posixify(filePath: string): string { + return filePath.replaceAll("\\", "/"); +} + +/** Trim trailing slashes but keep filesystem root as `/`. */ +function normalizeRoot(projectRoot: string): string { + const posix = posixify(projectRoot); + if (posix === "/" || /^[/\\]+$/.test(posix)) { + return "/"; + } + return posix.replace(/\/+$/, ""); +} + +/** Strip `projectRoot/` prefix so UI paths are project-relative. */ +export function toRelativePath(filePath: string, projectRoot: string): string { + const abs = posixify(filePath); + const root = normalizeRoot(projectRoot); + if (!root) { + return abs; + } + if (abs === root) { + return ""; + } + if (root === "/") { + return abs.startsWith("/") ? abs.slice(1) : abs; + } + const prefix = `${root}/`; + return abs.startsWith(prefix) ? abs.slice(prefix.length) : abs; +} diff --git a/packages/istanbul-lib-report/src/reports/html-modern/resolve-file-tags.ts b/packages/istanbul-lib-report/src/reports/html-modern/resolve-file-tags.ts new file mode 100644 index 0000000..6dd7087 --- /dev/null +++ b/packages/istanbul-lib-report/src/reports/html-modern/resolve-file-tags.ts @@ -0,0 +1,44 @@ +import picomatch from "picomatch"; + +import type { FileTagRule } from "./file-tags"; + +export { collectAvailableTags } from "./file-tags"; + +/** Map project-relative paths to tags matched by {@link rules}. */ +export function resolveFileTags( + relativePaths: readonly string[], + rules: FileTagRule[] | undefined, + options: { verbose?: boolean } = {}, +): Record { + if (rules === undefined || rules.length === 0) { + return {}; + } + + const seenTags = new Set(); + const matchers = rules.map((rule) => { + if (seenTags.has(rule.tag) && options.verbose) { + console.warn(`@vitest/istanbul-report-html-modern: duplicate fileTags tag "${rule.tag}"`); + } + seenTags.add(rule.tag); + return { + tag: rule.tag, + isMatch: picomatch(rule.glob, { dot: true }), + }; + }); + + const fileTagsByPath: Record = {}; + for (const relativePath of relativePaths) { + const normalizedPath = relativePath.replaceAll("\\", "/"); + const tags: string[] = []; + for (const matcher of matchers) { + if (matcher.isMatch(normalizedPath)) { + tags.push(matcher.tag); + } + } + if (tags.length > 0) { + fileTagsByPath[normalizedPath] = tags; + } + } + + return fileTagsByPath; +} diff --git a/packages/istanbul-lib-report/src/reports/html-modern/types.ts b/packages/istanbul-lib-report/src/reports/html-modern/types.ts index da416b0..cd364c6 100644 --- a/packages/istanbul-lib-report/src/reports/html-modern/types.ts +++ b/packages/istanbul-lib-report/src/reports/html-modern/types.ts @@ -1,7 +1,7 @@ import type { FileCoverageData } from "@vitest/istanbul-lib-coverage"; import type { Summarizers, Watermarks } from "../../index"; -import type { HtmlModernOptions } from "./options"; +import type { FileTagRule, HtmlModernOptions } from "./options"; /** coverage data keyed by absolute file path */ export type CoverageData = Record; @@ -46,6 +46,10 @@ export interface ReportData { projectRoot: string; coverage: CoverageData; sources: Record; + /** configured tag rules (snapshot for the HTML UI) */ + fileTagRules?: FileTagRule[]; + /** project-relative path → tags resolved at report generation time */ + fileTagsByPath?: Record; generatedAt?: string; packageName?: string; packageVersion?: string; diff --git a/packages/istanbul-lib-report/src/reports/html-modern/write-report.ts b/packages/istanbul-lib-report/src/reports/html-modern/write-report.ts index d248779..1167144 100644 --- a/packages/istanbul-lib-report/src/reports/html-modern/write-report.ts +++ b/packages/istanbul-lib-report/src/reports/html-modern/write-report.ts @@ -7,7 +7,7 @@ import type { ReportData } from "./types"; const require = createRequire(import.meta.url); -/** Must stay in sync with packages/istanbul-report-html-modern/report-data-placeholder.ts */ +/** Must stay in sync with `@vitest/istanbul-report-html-modern` `report-data-placeholder.ts`. */ export const REPORT_DATA_PLACEHOLDER = "__REPORT_DATA__"; export function resolveReportHtmlDist(): string | null { diff --git a/packages/istanbul-lib-report/test/reports/html-modern/resolve-file-tags.test.ts b/packages/istanbul-lib-report/test/reports/html-modern/resolve-file-tags.test.ts new file mode 100644 index 0000000..13038b5 --- /dev/null +++ b/packages/istanbul-lib-report/test/reports/html-modern/resolve-file-tags.test.ts @@ -0,0 +1,49 @@ +import { assert, describe, it } from "vitest"; + +import { + collectAvailableTags, + resolveFileTags, +} from "../../../src/reports/html-modern/resolve-file-tags"; + +describe("collectAvailableTags", () => { + it("returns unique tags in config order", () => { + assert.deepEqual( + collectAvailableTags([ + { glob: "src/a/**", tag: "team-a" }, + { glob: "src/b/**", tag: "team-b" }, + { glob: "src/c/**", tag: "team-a" }, + ]), + ["team-a", "team-b"], + ); + }); +}); + +describe("resolveFileTags", () => { + it("assigns multiple tags to one file", () => { + const tags = resolveFileTags( + ["src/payments/api.ts"], + [ + { glob: "src/payments/**", tag: "payments" }, + { glob: "**/*.ts", tag: "typescript" }, + ], + ); + + assert.deepEqual(tags["src/payments/api.ts"], ["payments", "typescript"]); + }); + + it("omits files with no matching tags", () => { + const tags = resolveFileTags( + ["src/auth/login.ts"], + [{ glob: "src/payments/**", tag: "payments" }], + ); + assert.deepEqual(tags, {}); + }); + + it("normalizes backslashes before matching", () => { + const tags = resolveFileTags( + ["src\\payments\\api.ts"], + [{ glob: "src/payments/**", tag: "payments" }], + ); + assert.deepEqual(tags["src/payments/api.ts"], ["payments"]); + }); +}); diff --git a/packages/istanbul-report-html-modern/.gitignore b/packages/istanbul-report-html-modern/.gitignore index 77db5ae..5bcb6fe 100644 --- a/packages/istanbul-report-html-modern/.gitignore +++ b/packages/istanbul-report-html-modern/.gitignore @@ -1,5 +1,3 @@ -node_modules dist -*.log -.DS_Store coverage +node_modules diff --git a/packages/istanbul-report-html-modern/README.md b/packages/istanbul-report-html-modern/README.md deleted file mode 100644 index f00117a..0000000 --- a/packages/istanbul-report-html-modern/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# @vitest/istanbul-report-html-modern - -Modern Istanbul HTML coverage report: - -- **React library** — `import { ReportApp } from "@vitest/istanbul-report-html-modern"` + `import "@vitest/istanbul-report-html-modern/style.css"` -- **Single-file HTML page** — `dist/page/index.html` (used by `@vitest/istanbul-lib-report`’s `html-modern` report) - -## Layout - -``` -src/ - lib/ # component library (tsdown → dist/index.js) - page/ # single-file HTML shell (vite → dist/page/index.html) -``` - -The page build imports only the public library API, not lib internals. - -## Scripts - -```bash -pnpm build # dist/index.js + style.css, then dist/page/index.html -pnpm dev # full report page; reads coverage/report-data.json (generate via pnpm test) -pnpm play # UI playground (library API) -``` diff --git a/packages/istanbul-report-html-modern/package.json b/packages/istanbul-report-html-modern/package.json index e138c62..dba5854 100644 --- a/packages/istanbul-report-html-modern/package.json +++ b/packages/istanbul-report-html-modern/package.json @@ -1,21 +1,21 @@ { "name": "@vitest/istanbul-report-html-modern", - "version": "1.0.0", - "description": "Modern Istanbul HTML coverage report: React UI + single-file HTML page", + "version": "1.0.1", + "description": "Modern Istanbul HTML coverage report: Preact UI + single-file HTML page", "keywords": [ "coverage", "html", "istanbul", - "react", + "preact", "report", "vitest" ], - "homepage": "https://vitest.dev/", + "homepage": "https://vitest.dev", "bugs": { "url": "https://github.com/vitest-dev/vitest/issues" }, "license": "MIT", - "author": "Travis Zhang", + "author": "Vitest Team", "repository": { "type": "git", "url": "git+https://github.com/vitest-dev/istanbuljs.git", @@ -42,34 +42,38 @@ "build:page": "vite build", "dev": "vite", "play": "vite --config vite.config.playground.ts", - "typecheck": "tsc --noEmit", - "preview": "vite preview" + "preview": "vite preview", + "typecheck": "tsc --noEmit" }, "dependencies": { - "@ant-design/icons": "catalog:", - "antd": "catalog:", - "monaco-editor": "catalog:", - "react-highlight-words": "catalog:" + "lucide-preact": "catalog:", + "picomatch": "catalog:" }, "devDependencies": { + "@codemirror/lang-javascript": "catalog:", + "@codemirror/language": "catalog:", + "@codemirror/state": "catalog:", + "@codemirror/theme-one-dark": "catalog:", + "@codemirror/view": "catalog:", + "@preact/preset-vite": "catalog:", "@tsdown/css": "catalog:", "@types/node": "catalog:", - "@types/react": "catalog:", - "@types/react-dom": "catalog:", - "@types/react-highlight-words": "catalog:", - "@vitejs/plugin-react": "catalog:", - "react": "catalog:", - "react-dom": "catalog:", + "@types/picomatch": "catalog:", + "@vitest/coverage-v8": "catalog:", + "@vitest/istanbul-lib-report": "workspace:*", + "preact": "catalog:", "tsdown": "catalog:", "typescript": "catalog:", "vite": "catalog:", - "vite-plugin-singlefile": "catalog:" + "vite-plugin-singlefile": "catalog:", + "vitest": "catalog:" }, "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" - }, - "engines": { - "node": ">=22" + "@codemirror/lang-javascript": "^6.2.0", + "@codemirror/language": "^6.11.0", + "@codemirror/state": "^6.5.0", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.38.0", + "preact": "^10.0.0" } } diff --git a/packages/istanbul-report-html-modern/playground/src/App.tsx b/packages/istanbul-report-html-modern/playground/src/App.tsx index 360fe4a..0bb1443 100644 --- a/packages/istanbul-report-html-modern/playground/src/App.tsx +++ b/packages/istanbul-report-html-modern/playground/src/App.tsx @@ -1,26 +1,46 @@ import reportData from "@repo/fixtures/report-data.json"; -import { buildReportFiles, ReportApp } from "@vitest/istanbul-report-html-modern"; +import { resolveFileTags } from "@vitest/istanbul-lib-report"; import "@vitest/istanbul-report-html-modern/style.css"; -import type { FileCoverageData } from "@vitest/istanbul-report-html-modern"; -import { useMemo } from "react"; +import { buildReportFiles, ReportApp } from "@vitest/istanbul-report-html-modern"; +import type { FileCoverageData, FileTagRule } from "@vitest/istanbul-report-html-modern"; +import { useMemo } from "preact/hooks"; + +import { toRelativePath } from "../../src/paths"; + +const playgroundFileTags: FileTagRule[] = [ + { glob: "src/**", tag: "src" }, + { glob: "src/helpers/**", tag: "helpers" }, + { glob: "src/components/**", tag: "ui" }, + { glob: "test/**", tag: "tests" }, +]; export function App() { - const prepared = useMemo( - () => - buildReportFiles({ - projectRoot: reportData.projectRoot, - coverage: reportData.coverage as Record, - sources: reportData.sources, - }), - [], - ); + const prepared = useMemo(() => { + const built = buildReportFiles({ + projectRoot: reportData.projectRoot, + coverage: reportData.coverage as Record, + sources: reportData.sources, + fileTagRules: playgroundFileTags, + }); + + const relativePaths = built.files.map((file) => toRelativePath(file.path, built.projectRoot)); + const fileTagsByPath = resolveFileTags(relativePaths, playgroundFileTags); + + return { + ...built, + fileTagRules: playgroundFileTags, + fileTagsByPath, + }; + }, []); return ( ); } diff --git a/packages/istanbul-report-html-modern/playground/src/index.tsx b/packages/istanbul-report-html-modern/playground/src/index.tsx index 8480740..14f27d6 100644 --- a/packages/istanbul-report-html-modern/playground/src/index.tsx +++ b/packages/istanbul-report-html-modern/playground/src/index.tsx @@ -1,11 +1,5 @@ -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; +import { render } from "preact"; -import { App } from "./App.tsx"; -import "monaco-editor-css"; +import { App } from "./App"; -createRoot(document.querySelector("#app")!).render( - - - , -); +render(, document.querySelector("#app")!); diff --git a/packages/istanbul-report-html-modern/playground/src/vite-env.d.ts b/packages/istanbul-report-html-modern/playground/src/vite-env.d.ts index e901c96..11f02fe 100644 --- a/packages/istanbul-report-html-modern/playground/src/vite-env.d.ts +++ b/packages/istanbul-report-html-modern/playground/src/vite-env.d.ts @@ -1,16 +1 @@ -declare module "monaco-editor-css" {} - -declare module "@repo/fixtures/report-data.json" { - const reportData: { - html: Record; - istanbul: Record; - stats: { - coverageFileCount: number; - sourceFileCount: number; - }; - projectRoot: string; - coverage: Record; - sources: Record; - }; - export default reportData; -} +/// diff --git a/packages/istanbul-report-html-modern/report-data-placeholder.ts b/packages/istanbul-report-html-modern/report-data-placeholder.ts index b56c352..ac4c253 100644 --- a/packages/istanbul-report-html-modern/report-data-placeholder.ts +++ b/packages/istanbul-report-html-modern/report-data-placeholder.ts @@ -1,2 +1,2 @@ -/** Placeholder replaced at report generation time; keep in sync with coverage-report.ts */ +/** Placeholder replaced at report generation time. */ export const REPORT_DATA_PLACEHOLDER = "__REPORT_DATA__"; diff --git a/packages/istanbul-report-html-modern/src/Report.tsx b/packages/istanbul-report-html-modern/src/Report.tsx new file mode 100644 index 0000000..6ff1be6 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/Report.tsx @@ -0,0 +1,153 @@ +import type { FunctionalComponent as FC } from "preact"; +import { useEffect, useId, useMemo, useRef, useState } from "preact/hooks"; + +import CoverageDetail from "./components/CoverageDetail"; +import SummaryHeader from "./components/SummaryHeader"; +import SummaryList from "./components/SummaryList"; +import SummaryTree from "./components/SummaryTree"; +import TopControl from "./components/TopControl"; +import { collectAvailableTags } from "./file-tags"; +import { deriveSummaryViews, filterDataSourceBase } from "./helpers/derive-views"; +import { emptyFileCoverage } from "./helpers/empty-coverage"; +import { ThemeProvider, useTheme } from "./theme-context"; +import type { FileCoverageData, ReportProps } from "./types"; + +const ReportContent: FC = ({ + value, + name, + dataSource, + onSelect, + fileTagRules, + statementWatermarks, +}) => { + const { theme } = useTheme(); + const [isLoading, setIsLoading] = useState(false); + const [filenameKeywords, setFilenameKeywords] = useState(""); + const [selectedTags, setSelectedTags] = useState([]); + const [showMode, setShowMode] = useState("tree"); + const [fileCoverage, setFileCoverage] = useState(emptyFileCoverage); + const [fileContent, setFileContent] = useState(""); + const rootId = useId().replaceAll(":", ""); + const rootClassName = `report-scope-${rootId} istanbul-html-modern`; + const onSelectRef = useRef(onSelect); + onSelectRef.current = onSelect; + + const requestSelect = (path: string) => { + void onSelectRef.current(path); + }; + + useEffect(() => { + let cancelled = false; + setIsLoading(true); + void onSelectRef + .current(value) + .then((res) => { + if (cancelled) { + return; + } + setFileContent(res.fileContent); + setFileCoverage(res.fileCoverage); + }) + .finally(() => { + if (!cancelled) { + setIsLoading(false); + } + }); + return () => { + cancelled = true; + }; + }, [value]); + + const isFile = useMemo(() => dataSource.some((item) => item.path === value), [dataSource, value]); + const mode = isFile ? "file" : showMode; + const isFileDataReady = isFile && !isLoading; + + const availableTags = useMemo(() => collectAvailableTags(fileTagRules), [fileTagRules]); + + const tagCounts = useMemo(() => { + const counts = new Map(); + for (const tag of availableTags) { + counts.set(tag, 0); + } + const baseRows = filterDataSourceBase({ dataSource, filenameKeywords, value }); + for (const row of baseRows) { + for (const tag of row.tags ?? []) { + counts.set(tag, (counts.get(tag) ?? 0) + 1); + } + } + return counts; + }, [availableTags, dataSource, filenameKeywords, value]); + + const { treeDataSource, rootDataSource, listDataSource } = useMemo( + () => + deriveSummaryViews({ + dataSource, + filenameKeywords, + selectedTags, + value, + }), + [dataSource, value, filenameKeywords, selectedTags], + ); + + return ( +
+ + + + {mode === "file" ? ( +
+ {isFileDataReady ? ( + + ) : ( +
+ Loading file… +
+ )} +
+ ) : ( +
+ {mode === "tree" && ( + + )} + {mode === "list" && ( + + )} +
+ )} +
+ ); +}; + +export const Report: FC = (props) => ( + + + +); + +export default Report; +export type { ReportProps }; diff --git a/packages/istanbul-report-html-modern/src/lib/ReportApp.tsx b/packages/istanbul-report-html-modern/src/ReportApp.tsx similarity index 65% rename from packages/istanbul-report-html-modern/src/lib/ReportApp.tsx rename to packages/istanbul-report-html-modern/src/ReportApp.tsx index d74905b..0807fc2 100644 --- a/packages/istanbul-report-html-modern/src/lib/ReportApp.tsx +++ b/packages/istanbul-report-html-modern/src/ReportApp.tsx @@ -1,4 +1,5 @@ -import { type FC, useCallback, useMemo } from "react"; +import type { FunctionalComponent as FC } from "preact"; +import { useCallback, useMemo } from "preact/hooks"; import { emptyFileCoverage } from "./helpers/empty-coverage"; import { filesToDataSource } from "./helpers/summary"; @@ -12,6 +13,9 @@ export const ReportApp: FC = ({ projectRoot, name, defaultValue = "", + fileTagRules, + fileTagsByPath, + statementWatermarks, }) => { const [value, setValue] = useHashPath(defaultValue); @@ -22,7 +26,16 @@ export const ReportApp: FC = ({ })); }, [files, projectRoot]); - const dataSource = useMemo(() => filesToDataSource(relativeFiles), [relativeFiles]); + const dataSource = useMemo(() => { + const rows = filesToDataSource(relativeFiles); + if (fileTagsByPath === undefined) { + return rows; + } + return rows.map((row) => ({ + ...row, + tags: fileTagsByPath[row.path] ?? [], + })); + }, [relativeFiles, fileTagsByPath]); const reportName = name ?? projectRootBaseName(projectRoot); const onSelect = useCallback( @@ -45,7 +58,14 @@ export const ReportApp: FC = ({ return (
- +
); }; diff --git a/packages/istanbul-report-html-modern/src/codemirror.ts b/packages/istanbul-report-html-modern/src/codemirror.ts new file mode 100644 index 0000000..92ea2fb --- /dev/null +++ b/packages/istanbul-report-html-modern/src/codemirror.ts @@ -0,0 +1,79 @@ +import { javascript } from "@codemirror/lang-javascript"; +import { defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language"; +import type { Extension } from "@codemirror/state"; +import { oneDarkHighlightStyle, oneDarkTheme } from "@codemirror/theme-one-dark"; +import { EditorView } from "@codemirror/view"; + +/** Map coverage file paths to CodeMirror language extensions (JS/TS only). */ +export function languageExtensionFromPath(filePath: string): Extension { + const ext = filePath.split(".").pop()?.toLowerCase(); + switch (ext) { + case "ts": + case "mts": + case "cts": + case "tsx": + return javascript({ typescript: true, jsx: ext === "tsx" }); + case "js": + case "mjs": + case "cjs": + case "jsx": + return javascript({ jsx: ext === "jsx" }); + default: + return []; + } +} + +export const coverageHighlight = syntaxHighlighting(defaultHighlightStyle, { fallback: true }); + +export const darkCoverageHighlight = syntaxHighlighting(oneDarkHighlightStyle, { fallback: true }); + +const baseEditorChrome = { + "&": { + height: "100%", + fontSize: "12px", + }, + ".cm-scroller": { + fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", + lineHeight: "18px", + overflow: "auto", + }, + ".cm-content": { + padding: "0", + caretColor: "transparent", + }, + ".cm-coverage-gutter": { + minWidth: "auto", + }, + ".cm-coverage-gutter .cm-gutterElement": { + padding: "0 4px 0 0", + }, +} as const; + +export const lightEditorTheme = EditorView.theme( + { + ...baseEditorChrome, + ".cm-gutters": { + border: "none", + backgroundColor: "var(--report-bg) !important", + }, + }, + { dark: false }, +); + +export const darkEditorTheme: Extension = [ + oneDarkTheme, + EditorView.theme( + { + ...baseEditorChrome, + "&": { + ...baseEditorChrome["&"], + backgroundColor: "var(--report-bg) !important", + }, + ".cm-gutters": { + border: "none", + backgroundColor: "var(--report-bg) !important", + }, + }, + { dark: true }, + ), +]; diff --git a/packages/istanbul-report-html-modern/src/components/CoverageDetail.tsx b/packages/istanbul-report-html-modern/src/components/CoverageDetail.tsx new file mode 100644 index 0000000..b5303c1 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/CoverageDetail.tsx @@ -0,0 +1,174 @@ +import { EditorState, Text } from "@codemirror/state"; +import type { Extension } from "@codemirror/state"; +import { Decoration, EditorView, WidgetType } from "@codemirror/view"; +import { useEffect, useMemo, useRef } from "preact/hooks"; + +import { + coverageHighlight, + darkCoverageHighlight, + darkEditorTheme, + languageExtensionFromPath, + lightEditorTheme, +} from "../codemirror"; +import { annotateBranches, annotateFunctions, annotateStatements } from "../helpers/annotate"; +import type { CoverageAnnotation } from "../helpers/annotate"; +import { emptyFileCoverage } from "../helpers/empty-coverage"; +import { computeLineHits } from "../helpers/line-hits"; +import type { ThemeMode } from "../theme-context"; +import type { FileCoverageData } from "../types"; +import { coverageLineGutter, type LineState } from "./LineNumbers"; + +const UNCOVERED_HOVER: Record = { + S: "Statement not covered", + F: "Function not covered", + B: "Branch not covered", + I: "If path not taken", + E: "Else path not taken", +}; + +/** Convert 1-based line/col (coverage annotation) to a CodeMirror document offset. */ +function posAt(doc: Text, line: number, col: number): number { + const clampedLine = Math.min(Math.max(line, 1), doc.lines); + const lineObj = doc.line(clampedLine); + return lineObj.from + Math.max(0, Math.min(col - 1, lineObj.length)); +} + +class BranchWidget extends WidgetType { + constructor(readonly kind: "I" | "E") { + super(); + } + + eq(other: BranchWidget): boolean { + return this.kind === other.kind; + } + + toDOM(): HTMLElement { + const span = document.createElement("span"); + span.className = this.kind === "I" ? "insert-i-decoration" : "insert-e-decoration"; + span.setAttribute("aria-label", UNCOVERED_HOVER[this.kind]); + span.title = UNCOVERED_HOVER[this.kind]; + return span; + } + + ignoreEvent(): boolean { + return false; + } +} + +function buildCoverageDecorations(doc: Text, annotations: CoverageAnnotation[]) { + const ranges = []; + + for (const item of annotations) { + const from = posAt(doc, item.startLine, item.startCol); + if (item.type === "I" || item.type === "E") { + ranges.push( + Decoration.widget({ + widget: new BranchWidget(item.type), + side: -1, + }).range(from), + ); + continue; + } + + const to = posAt(doc, item.endLine, item.endCol); + if (to <= from) { + continue; + } + ranges.push( + Decoration.mark({ + class: item.type === "B" ? "content-class-no-found-branch" : "content-class-no-found", + attributes: { title: UNCOVERED_HOVER[item.type] }, + }).range(from, to), + ); + } + + // Sort by `from` + `startSide` — RangeSetBuilder requires pre-sorted input. + return Decoration.set(ranges, true); +} + +function createEditorExtensions(options: { + filePath: string; + theme: ThemeMode; + linesState: LineState[]; + annotations: CoverageAnnotation[]; + doc: Text; +}): Extension[] { + const { filePath, theme, linesState, annotations, doc } = options; + const decorationSet = buildCoverageDecorations(doc, annotations); + + return [ + EditorState.readOnly.of(true), + EditorView.editable.of(false), + languageExtensionFromPath(filePath), + theme === "dark" ? darkEditorTheme : lightEditorTheme, + theme === "dark" ? darkCoverageHighlight : coverageHighlight, + coverageLineGutter(linesState), + EditorView.decorations.of(decorationSet), + ]; +} + +const CoverageDetail = ({ + source, + coverage, + theme, +}: { + source: string; + coverage: FileCoverageData; + theme: ThemeMode; +}) => { + const fileCoverage = coverage.path === "" ? emptyFileCoverage : coverage; + const { lines } = useMemo(() => computeLineHits(fileCoverage, source), [fileCoverage, source]); + const ref = useRef(null); + + const linesState = useMemo( + () => + lines.map((line, index) => ({ + lineNumber: index + 1, + hit: line.executionNumber, + })), + [lines], + ); + + const annotations = useMemo( + () => [ + ...annotateStatements(fileCoverage, source), + ...annotateFunctions(fileCoverage, source), + ...annotateBranches(fileCoverage, source), + ], + [fileCoverage, source], + ); + + useEffect(() => { + const dom = ref.current; + if (dom === null) { + return; + } + + const doc = Text.of(source.split(/\r?\n/)); + const view = new EditorView({ + parent: dom, + state: EditorState.create({ + doc, + extensions: createEditorExtensions({ + filePath: fileCoverage.path, + theme, + linesState, + annotations, + doc, + }), + }), + }); + + return () => { + view.destroy(); + }; + }, [source, fileCoverage.path, linesState, annotations, theme]); + + return ( +
+
+
+ ); +}; + +export default CoverageDetail; diff --git a/packages/istanbul-report-html-modern/src/components/CoverageMeter.tsx b/packages/istanbul-report-html-modern/src/components/CoverageMeter.tsx new file mode 100644 index 0000000..487c118 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/CoverageMeter.tsx @@ -0,0 +1,23 @@ +import type { FunctionalComponent as FC } from "preact"; + +import { getColor, type StatementWatermarks } from "../helpers/color"; + +/** Compact coverage percentage meter (antd Progress line–like). */ +export const CoverageMeter: FC<{ pct: number; statementWatermarks?: StatementWatermarks }> = ({ + pct, + statementWatermarks, +}) => { + const value = Number.isFinite(pct) ? Math.max(0, Math.min(100, pct)) : 0; + const label = Number.isInteger(value) ? `${value}%` : `${value.toFixed(1)}%`; + return ( +
+
+
+
+ {label} +
+ ); +}; diff --git a/packages/istanbul-report-html-modern/src/components/LineNumbers.tsx b/packages/istanbul-report-html-modern/src/components/LineNumbers.tsx new file mode 100644 index 0000000..9023ab7 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/LineNumbers.tsx @@ -0,0 +1,75 @@ +import type { Extension } from "@codemirror/state"; +import { gutter, GutterMarker } from "@codemirror/view"; + +export interface LineState { + lineNumber: number; + hit: number; +} + +function hitBackground(hit: number): string { + if (hit > 0) { + return "var(--report-line-hit-covered)"; + } + if (hit === 0) { + return "var(--report-line-hit-uncovered)"; + } + return "var(--report-line-hit-neutral)"; +} + +const GUTTER_CHAR_WIDTH = 7.2; + +/** Render coverage line-number gutter HTML for a single line. */ +export function renderLineNumberGutter(lineNumber: number, linesState: LineState[]): string { + const line = linesState.find((item) => item.lineNumber === lineNumber) ?? { + hit: -1, + lineNumber, + }; + + const lineDigitWidth = Math.max(1, linesState.length).toString().length; + const lineNumberWidth = lineDigitWidth * GUTTER_CHAR_WIDTH; + + const maxHit = Math.max(0, ...linesState.map((item) => item.hit)); + const hitDigitWidth = maxHit.toString().length; + const maxHitWidth = (hitDigitWidth + 2) * GUTTER_CHAR_WIDTH; + const hitLabel = line.hit > 0 ? `${line.hit}x` : ""; + + return `
${lineNumber}${hitLabel}
`; +} + +class CoverageGutterMarker extends GutterMarker { + constructor( + readonly lineNumber: number, + readonly hit: number, + readonly html: string, + ) { + super(); + } + + eq(other: CoverageGutterMarker): boolean { + return this.lineNumber === other.lineNumber && this.hit === other.hit; + } + + toDOM(): HTMLElement { + const wrap = document.createElement("div"); + wrap.innerHTML = this.html; + return (wrap.firstElementChild as HTMLElement | null) ?? wrap; + } +} + +/** CodeMirror gutter showing line numbers + hit counts. */ +export function coverageLineGutter(linesState: LineState[]): Extension { + const byLine = new Map(linesState.map((line) => [line.lineNumber, line])); + + return gutter({ + class: "cm-coverage-gutter", + lineMarker(view, line) { + const lineNumber = view.state.doc.lineAt(line.from).number; + const hit = byLine.get(lineNumber)?.hit ?? -1; + return new CoverageGutterMarker( + lineNumber, + hit, + renderLineNumberGutter(lineNumber, linesState), + ); + }, + }); +} diff --git a/packages/istanbul-report-html-modern/src/components/SummaryHeader.tsx b/packages/istanbul-report-html-modern/src/components/SummaryHeader.tsx new file mode 100644 index 0000000..c695434 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/SummaryHeader.tsx @@ -0,0 +1,93 @@ +import type { FunctionalComponent as FC } from "preact"; + +import { getColor, type StatementWatermarks } from "../helpers/color"; +import type { DataSourceItem } from "../types"; + +const SUMMARY_LABELS: Record = { + statements: "Statements", + branches: "Branches", + functions: "Functions", + lines: "Lines", +}; + +const METRIC_ORDER = ["statements", "branches", "functions", "lines"] as const; + +const SummaryNav: FC<{ + reportName: string; + value: string; + onClick: (value: string) => void; +}> = ({ value, onClick, reportName }) => { + const crumbs = value === "" ? [reportName] : `${reportName}/${value}`.split("/"); + + return ( + + ); +}; + +const SummaryMetric: FC<{ data: DataSourceItem }> = ({ data }) => { + return ( +
+ {METRIC_ORDER.map((key) => { + const value = data[key]; + return ( +
+ {value.pct}% + {SUMMARY_LABELS[key]}: + + {value.covered}/{value.total} + +
+ ); + })} +
+ ); +}; + +const SummaryBar: FC<{ pct: number; statementWatermarks?: StatementWatermarks }> = ({ + pct, + statementWatermarks, +}) => { + return ( +
+ + + + + + + + + + {rows.map((row) => ( + + + + + + + ))} + +
+ + {row.statements.total}{row.statements.covered} + +
+ {rows.length === 0 ?

No files match the current filter.

: null} +
+ ); +}; + +export default SummaryList; diff --git a/packages/istanbul-report-html-modern/src/components/SummaryTree.tsx b/packages/istanbul-report-html-modern/src/components/SummaryTree.tsx new file mode 100644 index 0000000..c799c9c --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/SummaryTree.tsx @@ -0,0 +1,107 @@ +import { File, Folder } from "lucide-preact"; +import type { FunctionalComponent as FC } from "preact"; +import { useMemo, useState } from "preact/hooks"; + +import type { StatementWatermarks } from "../helpers/color"; +import type { DataSourceItem } from "../types"; +import { CoverageMeter } from "./CoverageMeter"; +import { SortableTh, sortCoverageRows, type SortDir, type SortKey } from "./table-utils"; + +function isSourceFile(path: string): boolean { + return /\.(js|jsx|ts|tsx|mjs|cjs|mts|cts|vue|json|css|scss|less|html|md)$/i.test(path); +} + +const SummaryTree: FC<{ + dataSource: DataSourceItem[]; + onSelect: (path: string) => void; + statementWatermarks?: StatementWatermarks; +}> = ({ dataSource, onSelect, statementWatermarks }) => { + const [sortKey, setSortKey] = useState("path"); + const [sortDir, setSortDir] = useState("asc"); + + const rows = useMemo( + () => sortCoverageRows(dataSource, sortKey, sortDir), + [dataSource, sortKey, sortDir], + ); + + const onSort = (key: SortKey) => { + if (key === sortKey) { + setSortDir((prev) => (prev === "asc" ? "desc" : "asc")); + return; + } + setSortKey(key); + setSortDir(key === "path" ? "asc" : "desc"); + }; + + return ( +
+ + + + + + + + + + + {rows.map((row) => { + const name = row.path.split("/").at(-1) || row.path; + return ( + + + + + + + ); + })} + +
+ + {row.statements.total}{row.statements.covered} + +
+ {rows.length === 0 ?

No files match the current filters.

: null} +
+ ); +}; + +export default SummaryTree; diff --git a/packages/istanbul-report-html-modern/src/components/TagFilter.tsx b/packages/istanbul-report-html-modern/src/components/TagFilter.tsx new file mode 100644 index 0000000..cdd3cbf --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/TagFilter.tsx @@ -0,0 +1,107 @@ +import { Tag, X } from "lucide-preact"; +import type { FunctionalComponent as FC } from "preact"; +import { useEffect, useRef, useState } from "preact/hooks"; + +const TagFilter: FC<{ + availableTags: string[]; + tagCounts: ReadonlyMap; + selectedTags: string[]; + onChangeSelectedTags: (tags: string[]) => void; +}> = ({ availableTags, tagCounts, selectedTags, onChangeSelectedTags }) => { + const [open, setOpen] = useState(false); + const rootRef = useRef(null); + + useEffect(() => { + if (!open) { + return; + } + + const onPointerDown = (event: MouseEvent) => { + const root = rootRef.current; + if (root !== null && event.target instanceof Node && !root.contains(event.target)) { + setOpen(false); + } + }; + + document.addEventListener("mousedown", onPointerDown); + return () => document.removeEventListener("mousedown", onPointerDown); + }, [open]); + + if (availableTags.length === 0) { + return null; + } + + const toggleTag = (tag: string) => { + if (selectedTags.includes(tag)) { + onChangeSelectedTags(selectedTags.filter((item) => item !== tag)); + return; + } + onChangeSelectedTags([...selectedTags, tag]); + }; + + const clearTags = () => { + onChangeSelectedTags([]); + }; + + return ( +
+ + + {open ? ( +
+ {availableTags.map((tag) => { + const checked = selectedTags.includes(tag); + const count = tagCounts.get(tag) ?? 0; + return ( + + ); + })} + {selectedTags.length > 0 ? ( +
+ +
+ ) : null} +
+ ) : null} + + {selectedTags.length > 0 ? ( +
+ {selectedTags.map((tag) => ( + + ))} +
+ ) : null} +
+ ); +}; + +export default TagFilter; diff --git a/packages/istanbul-report-html-modern/src/components/TopControl.tsx b/packages/istanbul-report-html-modern/src/components/TopControl.tsx new file mode 100644 index 0000000..a957ae1 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/TopControl.tsx @@ -0,0 +1,86 @@ +import { List, ListTree, Moon, Sun } from "lucide-preact"; +import type { FunctionalComponent as FC } from "preact"; + +import { useTheme } from "../theme-context"; +import TagFilter from "./TagFilter"; + +const TopControl: FC<{ + total: number; + showMode: string; + filenameKeywords: string; + availableTags: string[]; + tagCounts: ReadonlyMap; + selectedTags: string[]; + onChangeSelectedTags: (tags: string[]) => void; + onChangeShowMode: (mode: string) => void; + onChangeKeywords: (word: string) => void; +}> = ({ + total, + showMode, + onChangeShowMode, + onChangeKeywords, + filenameKeywords, + availableTags, + tagCounts, + selectedTags, + onChangeSelectedTags, +}) => { + const { theme, toggleTheme } = useTheme(); + + return ( +
+
+
+
+ + +
+ {total} Total Files +
+ +
+
+ + onChangeKeywords(event.currentTarget.value)} + /> +
+ +
+
+
+ ); +}; + +export default TopControl; diff --git a/packages/istanbul-report-html-modern/src/components/table-utils.tsx b/packages/istanbul-report-html-modern/src/components/table-utils.tsx new file mode 100644 index 0000000..7d52438 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/components/table-utils.tsx @@ -0,0 +1,76 @@ +import type { ComponentChildren, FunctionalComponent as FC } from "preact"; + +/** Highlight case-insensitive substring matches; no-op when query is empty. */ +export function highlightMatch(text: string, query: string): ComponentChildren { + const needle = query.trim(); + if (!needle) { + return text; + } + + const lower = text.toLowerCase(); + const q = needle.toLowerCase(); + const parts: ComponentChildren[] = []; + let start = 0; + let index = lower.indexOf(q); + + while (index !== -1) { + if (index > start) { + parts.push(text.slice(start, index)); + } + parts.push( + + {text.slice(index, index + needle.length)} + , + ); + start = index + needle.length; + index = lower.indexOf(q, start); + } + + if (start < text.length) { + parts.push(text.slice(start)); + } + + return parts.length > 0 ? parts : text; +} + +type SortKey = "path" | "total" | "covered" | "pct"; +type SortDir = "asc" | "desc"; + +export function sortCoverageRows< + T extends { path: string; statements: { total: number; covered: number; pct: number } }, +>(rows: T[], key: SortKey, dir: SortDir): T[] { + const factor = dir === "asc" ? 1 : -1; + return [...rows].sort((a, b) => { + if (key === "path") { + return a.path.localeCompare(b.path) * factor; + } + if (key === "total") { + return (a.statements.total - b.statements.total) * factor; + } + if (key === "covered") { + return (a.statements.covered - b.statements.covered) * factor; + } + return (a.statements.pct - b.statements.pct) * factor; + }); +} + +export const SortableTh: FC<{ + label: string; + sortKey: SortKey; + activeKey: SortKey; + dir: SortDir; + onSort: (key: SortKey) => void; + className?: string; +}> = ({ label, sortKey, activeKey, dir, onSort, className }) => { + const active = activeKey === sortKey; + return ( + + + + ); +}; + +export type { SortKey, SortDir }; diff --git a/packages/istanbul-report-html-modern/src/lib/css.d.ts b/packages/istanbul-report-html-modern/src/css.d.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/lib/css.d.ts rename to packages/istanbul-report-html-modern/src/css.d.ts diff --git a/packages/istanbul-report-html-modern/src/file-tags.ts b/packages/istanbul-report-html-modern/src/file-tags.ts new file mode 100644 index 0000000..c4af5a0 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/file-tags.ts @@ -0,0 +1,25 @@ +/** glob rule that assigns a tag to matching project-relative file paths */ +export interface FileTagRule { + /** glob relative to project root, e.g. `src/payments/**` */ + glob: string; + /** display tag, e.g. `payments` or `P0` */ + tag: string; +} + +/** Unique tag names in config order. */ +export function collectAvailableTags(rules: FileTagRule[] | undefined): string[] { + if (rules === undefined || rules.length === 0) { + return []; + } + + const seen = new Set(); + const tags: string[] = []; + for (const rule of rules) { + if (seen.has(rule.tag)) { + continue; + } + seen.add(rule.tag); + tags.push(rule.tag); + } + return tags; +} diff --git a/packages/istanbul-report-html-modern/src/lib/hash-route.ts b/packages/istanbul-report-html-modern/src/hash-route.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/lib/hash-route.ts rename to packages/istanbul-report-html-modern/src/hash-route.ts diff --git a/packages/istanbul-report-html-modern/src/lib/helpers/annotate.ts b/packages/istanbul-report-html-modern/src/helpers/annotate.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/lib/helpers/annotate.ts rename to packages/istanbul-report-html-modern/src/helpers/annotate.ts diff --git a/packages/istanbul-report-html-modern/src/lib/helpers/build-report-files.ts b/packages/istanbul-report-html-modern/src/helpers/build-report-files.ts similarity index 62% rename from packages/istanbul-report-html-modern/src/lib/helpers/build-report-files.ts rename to packages/istanbul-report-html-modern/src/helpers/build-report-files.ts index 4cc0c73..b8931bc 100644 --- a/packages/istanbul-report-html-modern/src/lib/helpers/build-report-files.ts +++ b/packages/istanbul-report-html-modern/src/helpers/build-report-files.ts @@ -1,10 +1,20 @@ +import type { FileTagRule } from "../file-tags"; import { projectRootBaseName, resolveSource, toRelativePath } from "../paths"; import type { FileCoverageData, ReportAppFile } from "../types"; +import type { StatementWatermarks } from "./color"; export interface ReportDataLike { projectRoot?: string; coverage: Record; sources: Record; + html?: { fileTags?: FileTagRule[] }; + istanbul?: { + watermarks?: { + statements?: StatementWatermarks; + }; + }; + fileTagRules?: FileTagRule[]; + fileTagsByPath?: Record; } /** Build `ReportApp` files from serialized report payload (absolute paths + sources). */ @@ -12,6 +22,9 @@ export function buildReportFiles(reportData: ReportDataLike): { files: ReportAppFile[]; projectRoot: string; name: string; + fileTagRules?: FileTagRule[]; + fileTagsByPath?: Record; + statementWatermarks?: StatementWatermarks; } { const projectRoot = reportData.projectRoot ?? ""; const coverage = reportData.coverage as Record; @@ -29,5 +42,8 @@ export function buildReportFiles(reportData: ReportDataLike): { files, projectRoot, name: projectRootBaseName(projectRoot), + fileTagRules: reportData.fileTagRules ?? reportData.html?.fileTags, + fileTagsByPath: reportData.fileTagsByPath, + statementWatermarks: reportData.istanbul?.watermarks?.statements, }; } diff --git a/packages/istanbul-report-html-modern/src/helpers/color.ts b/packages/istanbul-report-html-modern/src/helpers/color.ts new file mode 100644 index 0000000..e150ce8 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/helpers/color.ts @@ -0,0 +1,25 @@ +/** istanbul default for `statements` watermarks: `[low, high]` */ +export type StatementWatermarks = readonly [low: number, high: number]; + +export const DEFAULT_STATEMENT_WATERMARKS: StatementWatermarks = [50, 80]; + +const COLORS = { + high: "rgb(33, 181, 119)", + medium: "rgb(244, 176, 27)", + low: "rgb(245, 32, 32)", +} as const; + +/** Map coverage percentage to a color using istanbul-style `[low, high]` watermarks. */ +export function getColor( + pct: number, + watermarks: StatementWatermarks = DEFAULT_STATEMENT_WATERMARKS, +): string { + const [low, high] = watermarks; + if (pct >= high) { + return COLORS.high; + } + if (pct >= low) { + return COLORS.medium; + } + return COLORS.low; +} diff --git a/packages/istanbul-report-html-modern/src/lib/helpers/derive-views.ts b/packages/istanbul-report-html-modern/src/helpers/derive-views.ts similarity index 60% rename from packages/istanbul-report-html-modern/src/lib/helpers/derive-views.ts rename to packages/istanbul-report-html-modern/src/helpers/derive-views.ts index 92d01fc..b1d6d55 100644 --- a/packages/istanbul-report-html-modern/src/lib/helpers/derive-views.ts +++ b/packages/istanbul-report-html-modern/src/helpers/derive-views.ts @@ -15,14 +15,42 @@ function underPath(item: { path: string }, startValue: string): boolean { return item.path === startValue || item.path.startsWith(`${startValue}/`); } +function matchesAnyTag(item: DataSourceItem, selectedTags: readonly string[]): boolean { + if (selectedTags.length === 0) { + return true; + } + const tags = item.tags; + if (tags === undefined || tags.length === 0) { + return false; + } + return selectedTags.some((tag) => tags.includes(tag)); +} + +/** Rows visible before tag filtering (path + keyword only). */ +export function filterDataSourceBase({ + dataSource, + filenameKeywords, + value, +}: { + dataSource: DataSourceItem[]; + filenameKeywords: string; + value: string; +}): DataSourceItem[] { + return dataSource.filter( + (item) => underPath(item, value) && matchesKeywords(item, filenameKeywords), + ); +} + /** Filter and group coverage rows for tree / list / header views. */ export function deriveSummaryViews({ dataSource, filenameKeywords, + selectedTags = [], value, }: { dataSource: DataSourceItem[]; filenameKeywords: string; + selectedTags?: string[]; value: string; }): { treeDataSource: DataSourceItem[]; @@ -30,7 +58,10 @@ export function deriveSummaryViews({ listDataSource: DataSourceItem[]; } { const listDataSource = dataSource.filter( - (item) => underPath(item, value) && matchesKeywords(item, filenameKeywords), + (item) => + underPath(item, value) && + matchesKeywords(item, filenameKeywords) && + matchesAnyTag(item, selectedTags), ); const tree = buildSummaryTree(value, listDataSource); return { diff --git a/packages/istanbul-report-html-modern/src/lib/helpers/empty-coverage.ts b/packages/istanbul-report-html-modern/src/helpers/empty-coverage.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/lib/helpers/empty-coverage.ts rename to packages/istanbul-report-html-modern/src/helpers/empty-coverage.ts diff --git a/packages/istanbul-report-html-modern/src/lib/helpers/line-hits.ts b/packages/istanbul-report-html-modern/src/helpers/line-hits.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/lib/helpers/line-hits.ts rename to packages/istanbul-report-html-modern/src/helpers/line-hits.ts diff --git a/packages/istanbul-report-html-modern/src/lib/helpers/summary.ts b/packages/istanbul-report-html-modern/src/helpers/summary.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/lib/helpers/summary.ts rename to packages/istanbul-report-html-modern/src/helpers/summary.ts diff --git a/packages/istanbul-report-html-modern/src/index.css b/packages/istanbul-report-html-modern/src/index.css new file mode 100644 index 0000000..f353499 --- /dev/null +++ b/packages/istanbul-report-html-modern/src/index.css @@ -0,0 +1,699 @@ +:root, +[data-theme="light"] { + color-scheme: light; + --report-bg: #ffffff; + --report-text: rgba(0, 0, 0, 0.88); + --report-muted: rgba(0, 0, 0, 0.45); + --report-secondary: rgba(0, 0, 0, 0.65); + --report-border: #d9d9d9; + --report-border-secondary: #f0f0f0; + --report-surface: #fafafa; + --report-surface-hover: #f5f5f5; + --report-control: #f5f5f5; + --report-active-bg: #ffffff; + --report-link: #0071c2; + --report-link-hover: #4096ff; + --report-primary: #0071c2; + --report-primary-shadow: rgba(5, 145, 255, 0.1); + --report-radius: 3px; + --report-radius-sm: 2px; + --report-line-coverage-bg: #eaeaea; + --report-line-coverage-text: rgba(0, 0, 0, 0.5); + --report-line-hit-covered: rgb(230, 245, 208); + --report-line-hit-uncovered: #f3aeac; + --report-line-hit-neutral: rgb(234, 234, 234); + --report-highlight-bg: #faad14; + --report-highlight-text: #ffffff; + --report-uncovered-branch-bg: rgba(255, 229, 100, 0.6); + --report-uncovered-bg: #f3aeac; + --report-decoration-bg: #000000; + --report-decoration-text: #ffff00; + --report-tag-bg: #f5f5f5; + --report-tag-border: #d9d9d9; +} + +[data-theme="dark"] { + color-scheme: dark; + --report-bg: #141414; + --report-text: rgba(255, 255, 255, 0.85); + --report-muted: rgba(255, 255, 255, 0.45); + --report-secondary: rgba(255, 255, 255, 0.65); + --report-border: #424242; + --report-border-secondary: #303030; + --report-surface: #1e1e1e; + --report-surface-hover: #262626; + --report-control: #1e1e1e; + --report-active-bg: #000000; + --report-link: #1668dc; + --report-link-hover: #3c89e8; + --report-primary: #1668dc; + --report-primary-shadow: rgba(9, 88, 217, 0.25); + --report-radius: 3px; + --report-radius-sm: 2px; + --report-line-coverage-bg: #303030; + --report-line-coverage-text: rgba(255, 255, 255, 0.45); + --report-line-hit-covered: rgb(45, 74, 34); + --report-line-hit-uncovered: #5c3534; + --report-line-hit-neutral: #303030; + --report-highlight-bg: #d48806; + --report-highlight-text: #ffffff; + --report-uncovered-branch-bg: rgba(180, 140, 0, 0.35); + --report-uncovered-bg: #5c3534; + --report-decoration-bg: #434343; + --report-decoration-text: #fadb14; + --report-tag-bg: #272727; + --report-tag-border: #424242; +} + +html, +body, +#root { + margin: 0; + height: 100%; + background: var(--report-bg); + color: var(--report-text); +} + +#app { + height: 100%; +} + +body { + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", + sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 14px; + line-height: 1.5715; +} + +.istanbul-html-modern { + height: 100%; + display: flex; + flex-direction: column; + gap: 6px; + padding: 8px; + box-sizing: border-box; + background: var(--report-bg); + color: var(--report-text); +} + +.istanbul-html-modern .report-scroll-body { + flex: 1; + min-height: 0; + overflow: auto; + padding-right: 3px; +} + +.istanbul-html-modern .report-editor-body { + flex: 1; + min-height: 0; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.istanbul-html-modern .report-loading { + display: grid; + place-items: center; + height: 100%; + color: var(--report-muted); + font-size: 14px; +} + +.istanbul-html-modern a, +.istanbul-html-modern .path-link, +.istanbul-html-modern .summary-nav__link { + color: var(--report-link); + cursor: pointer; + transition: color 0.2s; +} + +.istanbul-html-modern a:hover, +.istanbul-html-modern .path-link:hover, +.istanbul-html-modern .summary-nav__link:hover:not(.is-current) { + color: var(--report-link-hover); +} + +/* Top control — Segmented + Input feel */ +.istanbul-html-modern .top-control { + border-bottom: 1px solid var(--report-border-secondary); + padding-bottom: 8px; + margin-bottom: 2px; +} + +.istanbul-html-modern .top-control__row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; +} + +.istanbul-html-modern .top-control__left, +.istanbul-html-modern .top-control__right { + display: flex; + align-items: center; + gap: 8px; +} + +.istanbul-html-modern .top-control__filters { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} + +.istanbul-html-modern .top-control__count { + font-size: 14px; + color: var(--report-text); +} + +.istanbul-html-modern .view-toggle { + display: inline-flex; + padding: 2px; + border-radius: var(--report-radius); + background: var(--report-control); +} + +.istanbul-html-modern .view-toggle__btn { + display: inline-flex; + align-items: center; + gap: 6px; + border: 0; + background: transparent; + color: var(--report-text); + font: inherit; + font-size: 14px; + line-height: 1.1; + padding: 2px 11px; + border-radius: var(--report-radius-sm); + cursor: pointer; + transition: + background 0.2s, + color 0.2s, + box-shadow 0.2s; +} + +.istanbul-html-modern .view-toggle__btn:hover { + color: var(--report-text); +} + +.istanbul-html-modern .view-toggle__btn.is-active { + background: var(--report-active-bg); + color: var(--report-text); + box-shadow: + 0 1px 2px 0 rgba(0, 0, 0, 0.03), + 0 1px 6px -1px rgba(0, 0, 0, 0.02), + 0 2px 4px 0 rgba(0, 0, 0, 0.02); +} + +.istanbul-html-modern .icon-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: 0; + border-radius: var(--report-radius-sm); + background: transparent; + color: var(--report-secondary); + cursor: pointer; + transition: + color 0.2s, + background 0.2s; +} + +.istanbul-html-modern .icon-btn:hover { + color: var(--report-text); + background: var(--report-surface-hover); +} + +.istanbul-html-modern .search-input { + width: min(240px, 50vw); + height: 24px; + border: 1px solid var(--report-border); + border-radius: var(--report-radius); + background: var(--report-bg); + color: var(--report-text); + padding: 0 11px; + font: inherit; + font-size: 14px; + line-height: 1.5715; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.istanbul-html-modern .search-input::placeholder { + color: var(--report-muted); +} + +.istanbul-html-modern .search-input:hover { + border-color: var(--report-link-hover); +} + +.istanbul-html-modern .search-input:focus { + border-color: var(--report-primary); + outline: none; + box-shadow: 0 0 0 2px var(--report-primary-shadow); +} + +.istanbul-html-modern .tag-filter { + position: relative; + display: inline-flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} + +.istanbul-html-modern .tag-filter__trigger { + display: inline-flex; + align-items: center; + gap: 6px; + height: 24px; + border: 1px solid var(--report-border); + border-radius: var(--report-radius); + background: var(--report-bg); + color: var(--report-text); + font: inherit; + font-size: 14px; + padding: 0 10px; + cursor: pointer; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.istanbul-html-modern .tag-filter__trigger:hover, +.istanbul-html-modern .tag-filter__trigger.is-active { + border-color: var(--report-link-hover); +} + +.istanbul-html-modern .tag-filter__trigger:focus-visible { + border-color: var(--report-primary); + outline: none; + box-shadow: 0 0 0 2px var(--report-primary-shadow); +} + +.istanbul-html-modern .tag-filter__badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 16px; + height: 16px; + padding: 0 4px; + border-radius: 999px; + background: var(--report-primary); + color: #fff; + font-size: 11px; + line-height: 1; +} + +.istanbul-html-modern .tag-filter__menu { + position: absolute; + top: calc(100% + 4px); + left: 0; + z-index: 20; + min-width: 180px; + max-height: 280px; + overflow: auto; + border: 1px solid var(--report-border); + border-radius: var(--report-radius); + background: var(--report-bg); + box-shadow: + 0 6px 16px 0 rgba(0, 0, 0, 0.08), + 0 3px 6px -4px rgba(0, 0, 0, 0.12); + padding: 4px; +} + +.istanbul-html-modern .tag-filter__option { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 8px; + border-radius: var(--report-radius-sm); + cursor: pointer; +} + +.istanbul-html-modern .tag-filter__option:hover { + background: var(--report-surface-hover); +} + +.istanbul-html-modern .tag-filter__option-label { + flex: 1; +} + +.istanbul-html-modern .tag-filter__option-count { + color: var(--report-muted); + font-size: 12px; + font-variant-numeric: tabular-nums; +} + +.istanbul-html-modern .tag-filter__menu-footer { + border-top: 1px solid var(--report-border-secondary); + padding: 4px; + margin-top: 4px; +} + +.istanbul-html-modern .tag-filter__clear { + border: 0; + background: transparent; + color: var(--report-link); + font: inherit; + font-size: 13px; + padding: 4px 8px; + cursor: pointer; +} + +.istanbul-html-modern .tag-filter__clear:hover { + color: var(--report-link-hover); +} + +.istanbul-html-modern .tag-filter__chips { + display: inline-flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.istanbul-html-modern .tag-filter__chip { + display: inline-flex; + align-items: center; + gap: 4px; + height: 22px; + padding: 0 6px; + border: 1px solid var(--report-tag-border); + border-radius: var(--report-radius-sm); + background: var(--report-tag-bg); + color: var(--report-text); + font: inherit; + font-size: 12px; + cursor: pointer; +} + +.istanbul-html-modern .tag-filter__chip:hover { + border-color: var(--report-link-hover); +} + +/* Summary header */ +.istanbul-html-modern .summary-header { + display: flex; + flex-direction: column; + gap: 6px; +} + +.istanbul-html-modern .summary-nav { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; + font-size: 16px; + font-weight: 600; + line-height: 1.5; +} + +.istanbul-html-modern .summary-nav__crumb { + display: inline-flex; + align-items: center; + gap: 4px; +} + +.istanbul-html-modern .summary-nav__link { + border: 0; + background: transparent; + font: inherit; + font-weight: inherit; + padding: 0; +} + +.istanbul-html-modern .summary-nav__link.is-current { + color: var(--report-text); + cursor: default; +} + +.istanbul-html-modern .summary-nav__sep { + color: var(--report-text); + font-weight: bold; +} + +.istanbul-html-modern .summary-metrics { + display: flex; + flex-wrap: wrap; + gap: 6px 12px; + max-width: 1000px; +} + +.istanbul-html-modern .summary-metric { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 14px; +} + +.istanbul-html-modern .summary-metric__pct { + font-weight: 600; +} + +.istanbul-html-modern .summary-metric__label { + color: var(--report-secondary); +} + +.istanbul-html-modern .summary-metric__ratio { + display: inline-flex; + align-items: center; + height: 22px; + padding: 0 7px; + border-radius: var(--report-radius-sm); + background: var(--report-tag-bg); + color: var(--report-text); + font-size: 12px; + line-height: 20px; + font-variant-numeric: tabular-nums; +} + +.istanbul-html-modern .summary-bar { + height: 8px; + width: 100%; + border-radius: 0; +} + +/* Tables — bordered compact table */ +.istanbul-html-modern .coverage-table-wrap { + width: 100%; + border: 1px solid var(--report-border-secondary); + border-radius: var(--report-radius); + overflow: hidden; +} + +.istanbul-html-modern .coverage-table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + font-size: 14px; +} + +.istanbul-html-modern .coverage-table th, +.istanbul-html-modern .coverage-table td { + border-bottom: 1px solid var(--report-border-secondary); + border-inline-end: 1px solid var(--report-border-secondary); + padding: 8px 8px; + text-align: left; + vertical-align: middle; +} + +.istanbul-html-modern .coverage-table th:last-child, +.istanbul-html-modern .coverage-table td:last-child { + border-inline-end: 0; +} + +.istanbul-html-modern .coverage-table tbody tr:last-child td { + border-bottom: 0; +} + +.istanbul-html-modern .coverage-table thead th { + position: sticky; + top: 0; + background: var(--report-surface); + z-index: 1; + color: var(--report-text); + font-weight: 600; +} + +.istanbul-html-modern .coverage-table tbody tr:hover > td { + background: var(--report-surface-hover); +} + +.istanbul-html-modern .coverage-table .is-num { + width: 88px; + text-align: right; + font-variant-numeric: tabular-nums; +} + +.istanbul-html-modern .coverage-table .is-coverage { + width: 240px; +} + +.istanbul-html-modern .coverage-th-btn { + border: 0; + background: transparent; + color: inherit; + font: inherit; + font-weight: inherit; + padding: 0; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 4px; +} + +.istanbul-html-modern .coverage-th-btn:hover { + color: var(--report-primary); +} + +.istanbul-html-modern .coverage-th-btn__dir { + font-size: 12px; + color: var(--report-primary); +} + +.istanbul-html-modern .path-link { + display: inline-flex; + align-items: center; + gap: 4px; + border: 0; + background: transparent; + font: inherit; + padding: 0; + text-align: left; +} + +.istanbul-html-modern .path-icon { + flex-shrink: 0; + color: var(--report-secondary); + font-size: 16px; +} + +.istanbul-html-modern .search-mark { + padding: 0; + color: var(--report-highlight-text); + background: var(--report-highlight-bg); +} + +.istanbul-html-modern .empty-hint { + margin: 16px 0; + color: var(--report-muted); + font-size: 14px; + text-align: center; +} + +/* Progress-like meter */ +.istanbul-html-modern .coverage-meter { + display: flex; + align-items: center; + gap: 8px; + padding-right: 5px; +} + +.istanbul-html-modern .coverage-meter__track { + flex: 1; + height: 6px; + border-radius: 0; + background: var(--report-border-secondary); + overflow: hidden; +} + +.istanbul-html-modern .coverage-meter__fill { + height: 100%; + border-radius: 0; + transition: width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); +} + +.istanbul-html-modern .coverage-meter__label { + flex: none; + min-width: 2.5em; + text-align: left; + font-size: 12px; + line-height: 1; + font-variant-numeric: tabular-nums; + color: var(--report-text); +} + +/* Code viewer */ +.istanbul-html-modern .line-number { + margin-right: 5px; + font-variant-numeric: tabular-nums; +} + +.istanbul-html-modern .line-number-wrapper { + display: flex; + justify-self: end; +} + +.istanbul-html-modern .line-number-wrapper > span { + display: block; + text-align: right; +} + +.istanbul-html-modern .line-coverage { + background-color: var(--report-line-coverage-bg); + width: 10px; + padding-right: 5px; + color: var(--report-line-coverage-text); +} + +.istanbul-html-modern .content-class-no-found-branch { + background: var(--report-uncovered-branch-bg); +} + +.istanbul-html-modern .content-class-no-found { + background: var(--report-uncovered-bg); +} + +.istanbul-html-modern .insert-e-decoration::before { + content: "E"; + background-color: var(--report-decoration-bg); + color: var(--report-decoration-text); + padding: 0 2px; + border-radius: 2px; + margin-right: 5px; +} + +.istanbul-html-modern .insert-i-decoration::before { + content: "I"; + background-color: var(--report-decoration-bg); + color: var(--report-decoration-text); + padding: 0 2px; + border-radius: 2px; + margin-right: 5px; +} + +.istanbul-html-modern .coverage-detail-container { + height: 100%; + min-height: 0; + position: relative; + overflow: hidden; +} + +.istanbul-html-modern .coverage-detail-editor { + width: 100%; + height: 100%; + min-height: 0; + overflow: hidden; +} + +.istanbul-html-modern .coverage-detail-editor .cm-editor { + height: 100%; + max-height: 100%; + background: var(--report-bg); +} + +[data-theme="light"] .istanbul-html-modern .coverage-detail-editor .cm-editor { + color: var(--report-text); +} + +.istanbul-html-modern .coverage-detail-editor .cm-editor.cm-focused { + outline: none; +} + +.istanbul-html-modern .coverage-detail-editor .cm-scroller { + overflow: auto !important; +} diff --git a/packages/istanbul-report-html-modern/src/lib/index.ts b/packages/istanbul-report-html-modern/src/index.ts similarity index 84% rename from packages/istanbul-report-html-modern/src/lib/index.ts rename to packages/istanbul-report-html-modern/src/index.ts index e574849..de874cd 100644 --- a/packages/istanbul-report-html-modern/src/lib/index.ts +++ b/packages/istanbul-report-html-modern/src/index.ts @@ -4,6 +4,8 @@ export { Report } from "./Report"; export { ReportApp } from "./ReportApp"; export { buildReportFiles } from "./helpers/build-report-files"; export type { ReportDataLike } from "./helpers/build-report-files"; +export type { FileTagRule } from "./file-tags"; +export { collectAvailableTags } from "./file-tags"; export { filesToDataSource, buildSummaryTree } from "./helpers/summary"; export { projectRootBaseName, resolveSource, toAbsolutePath, toRelativePath } from "./paths"; export type { diff --git a/packages/istanbul-report-html-modern/src/lib/Report.tsx b/packages/istanbul-report-html-modern/src/lib/Report.tsx deleted file mode 100644 index 4aad74c..0000000 --- a/packages/istanbul-report-html-modern/src/lib/Report.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { ConfigProvider, Spin, theme as antdTheme } from "antd"; -import { type FC, useEffect, useId, useMemo, useRef, useState } from "react"; - -import CoverageDetail from "./components/CoverageDetail"; -import SummaryHeader from "./components/SummaryHeader"; -import SummaryList from "./components/SummaryList"; -import SummaryTree from "./components/SummaryTree"; -import TopControl from "./components/TopControl"; -import { deriveSummaryViews } from "./helpers/derive-views"; -import { emptyFileCoverage } from "./helpers/empty-coverage"; -import { ThemeProvider, useTheme } from "./theme-context"; -import type { FileCoverageData, ReportProps } from "./types"; - -const ReportContent: FC = ({ value, name, dataSource, onSelect }) => { - const { theme } = useTheme(); - const [isLoading, setIsLoading] = useState(false); - const [filenameKeywords, setFilenameKeywords] = useState(""); - const [showMode, setShowMode] = useState("tree"); - const [fileCoverage, setFileCoverage] = useState(emptyFileCoverage); - const [fileContent, setFileContent] = useState(""); - const rootId = useId().replaceAll(":", ""); - const rootClassName = `report-scope-${rootId} istanbul-html-modern`; - const onSelectRef = useRef(onSelect); - onSelectRef.current = onSelect; - - const requestSelect = (path: string) => { - void onSelectRef.current(path); - }; - - useEffect(() => { - let cancelled = false; - setIsLoading(true); - void onSelectRef - .current(value) - .then((res) => { - if (cancelled) { - return; - } - setFileContent(res.fileContent); - setFileCoverage(res.fileCoverage); - }) - .finally(() => { - if (!cancelled) { - setIsLoading(false); - } - }); - return () => { - cancelled = true; - }; - }, [value]); - - const isFile = useMemo(() => dataSource.some((item) => item.path === value), [dataSource, value]); - const mode = isFile ? "file" : showMode; - const isFileDataReady = isFile && !isLoading; - - const { treeDataSource, rootDataSource, listDataSource } = useMemo( - () => - deriveSummaryViews({ - dataSource, - filenameKeywords, - value, - }), - [dataSource, value, filenameKeywords], - ); - - return ( - -
- - - - {mode === "file" ? ( -
- - {isFileDataReady && ( - - )} - -
- ) : ( -
- {mode === "tree" && ( - - )} - {mode === "list" && ( - - )} -
- )} -
-
- ); -}; - -export const Report: FC = (props) => ( - - - -); - -export default Report; -export type { ReportProps }; diff --git a/packages/istanbul-report-html-modern/src/lib/components/CoverageDetail.tsx b/packages/istanbul-report-html-modern/src/lib/components/CoverageDetail.tsx deleted file mode 100644 index 25fdb0f..0000000 --- a/packages/istanbul-report-html-modern/src/lib/components/CoverageDetail.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { useEffect, useMemo, useRef } from "react"; - -import { annotateBranches, annotateFunctions, annotateStatements } from "../helpers/annotate"; -import type { CoverageAnnotation } from "../helpers/annotate"; -import { emptyFileCoverage } from "../helpers/empty-coverage"; -import { computeLineHits } from "../helpers/line-hits"; -import { languageFromPath, monaco } from "../monaco"; -import type { ThemeMode } from "../theme-context"; -import type { FileCoverageData } from "../types"; -import { renderLineNumberGutter } from "./LineNumbers"; - -const UNCOVERED_HOVER: Record = { - S: "Statement not covered", - F: "Function not covered", - B: "Branch not covered", - I: "If path not taken", - E: "Else path not taken", -}; - -function hoverMessage(type: CoverageAnnotation["type"]): monaco.IMarkdownString { - return { value: UNCOVERED_HOVER[type] }; -} - -const CoverageDetail = ({ - source, - coverage, - theme, -}: { - source: string; - coverage: FileCoverageData; - theme: ThemeMode; -}) => { - const fileCoverage = coverage.path === "" ? emptyFileCoverage : coverage; - const { lines } = useMemo(() => computeLineHits(fileCoverage, source), [fileCoverage, source]); - const ref = useRef(null); - const editorRef = useRef | null>(null); - - const linesState = useMemo( - () => - lines.map((line, index) => ({ - lineNumber: index + 1, - hit: line.executionNumber, - })), - [lines], - ); - - const lineNumbersMinChars = useMemo(() => { - const maxHit = Math.max(0, ...linesState.map((line) => line.hit)); - return maxHit.toString().length + 7; - }, [linesState]); - - useEffect(() => { - const dom = ref.current; - if (dom === null) { - return; - } - - const monacoTheme = theme === "dark" ? "vs-dark" : "vs"; - - const editor = monaco.editor.create(dom, { - value: source, - language: languageFromPath(fileCoverage.path), - theme: monacoTheme, - fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", - lineHeight: 18, - lineNumbers: (lineNumber: number) => renderLineNumberGutter(lineNumber, linesState), - lineNumbersMinChars, - readOnly: true, - folding: false, - minimap: { enabled: false }, - scrollBeyondLastLine: false, - showUnused: false, - fontSize: 12, - contextmenu: false, - automaticLayout: true, - links: false, - quickSuggestions: false, - wordBasedSuggestions: "off", - }); - editorRef.current = editor; - - const all = [ - ...annotateStatements(fileCoverage, source), - ...annotateFunctions(fileCoverage, source), - ...annotateBranches(fileCoverage, source), - ]; - - const decorations: monaco.editor.IModelDeltaDecoration[] = []; - for (const item of all) { - const hover = hoverMessage(item.type); - if (item.type === "S" || item.type === "F") { - decorations.push({ - range: new monaco.Range(item.startLine, item.startCol, item.endLine, item.endCol), - options: { - isWholeLine: false, - inlineClassName: "content-class-no-found", - hoverMessage: hover, - }, - }); - } else if (item.type === "B") { - decorations.push({ - range: new monaco.Range(item.startLine, item.startCol, item.endLine, item.endCol), - options: { - isWholeLine: false, - inlineClassName: "content-class-no-found-branch", - hoverMessage: hover, - }, - }); - } else if (item.type === "I") { - decorations.push({ - range: new monaco.Range(item.startLine, item.startCol, item.startLine, item.startCol), - options: { - beforeContentClassName: "insert-i-decoration", - hoverMessage: hover, - stickiness: monaco.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - }, - }); - } else if (item.type === "E") { - decorations.push({ - range: new monaco.Range(item.startLine, item.startCol, item.startLine, item.startCol), - options: { - beforeContentClassName: "insert-e-decoration", - hoverMessage: hover, - stickiness: monaco.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - }, - }); - } - } - - editor.createDecorationsCollection(decorations); - - return () => { - editor.dispose(); - editorRef.current = null; - }; - }, [source, fileCoverage, linesState, lineNumbersMinChars, theme]); - - useEffect(() => { - monaco.editor.setTheme(theme === "dark" ? "vs-dark" : "vs"); - }, [theme]); - - return ( -
-
-
- ); -}; - -export default CoverageDetail; diff --git a/packages/istanbul-report-html-modern/src/lib/components/LineNumbers.tsx b/packages/istanbul-report-html-modern/src/lib/components/LineNumbers.tsx deleted file mode 100644 index fae538e..0000000 --- a/packages/istanbul-report-html-modern/src/lib/components/LineNumbers.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { renderToStaticMarkup } from "react-dom/server"; - -interface LineState { - lineNumber: number; - hit: number; -} - -function hitBackground(hit: number): string { - if (hit > 0) { - return "var(--report-line-hit-covered)"; - } - if (hit === 0) { - return "var(--report-line-hit-uncovered)"; - } - return "var(--report-line-hit-neutral)"; -} - -function LineNumberWrapper({ - lineNumber, - line, - maxHitWidth, -}: { - lineNumber: number; - line: LineState; - maxHitWidth: number; -}) { - return ( -
- {lineNumber} - - {line.hit > 0 ? `${line.hit}x` : ""} - -
- ); -} - -/** Render Monaco line-number gutter HTML for a single line. */ -export function renderLineNumberGutter(lineNumber: number, linesState: LineState[]): string { - const line = linesState.find((item) => item.lineNumber === lineNumber) ?? { - hit: -1, - lineNumber, - }; - - const maxHit = Math.max(0, ...linesState.map((item) => item.hit)); - const digitWidth = maxHit.toString().length; - const maxHitWidth = (digitWidth + 2) * 7.2; - - return renderToStaticMarkup( - , - ); -} diff --git a/packages/istanbul-report-html-modern/src/lib/components/SummaryHeader.tsx b/packages/istanbul-report-html-modern/src/lib/components/SummaryHeader.tsx deleted file mode 100644 index 8e7c019..0000000 --- a/packages/istanbul-report-html-modern/src/lib/components/SummaryHeader.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Tag, Typography } from "antd"; -import type { FC } from "react"; - -import { getColor } from "../helpers/color"; -import type { DataSourceItem } from "../types"; - -const { Text } = Typography; - -const SUMMARY_LABELS: Record = { - statements: "Statements", - branches: "Branches", - functions: "Functions", - lines: "Lines", -}; - -const METRIC_ORDER = ["statements", "branches", "functions", "lines"] as const; - -const SummaryNav: FC<{ - reportName: string; - value: string; - onClick: (value: string) => void; -}> = ({ value, onClick, reportName }) => { - const crumbs = value === "" ? [reportName] : `${reportName}/${value}`.split("/"); - - return ( -
- {crumbs.map((item, index) => { - const pathKey = `${reportName}-${index}-${item}`; - return ( -
- { - onClick(value.split("/").slice(0, index).join("/")); - }} - > - {item} - - {index === value.split("/").length || !value ? null : /} -
- ); - })} -
- ); -}; - -const SummaryMetric: FC<{ data: DataSourceItem }> = ({ data }) => { - return ( -
-
- {METRIC_ORDER.map((key) => { - const value = data[key]; - return ( -
- {value.pct}% - - {SUMMARY_LABELS[key]}: - - - {value.covered}/{value.total} - -
- ); - })} -
-
- ); -}; - -const SummaryBar: FC<{ pct: number }> = ({ pct }) => { - return ( -
- ); -}; - -const SummaryHeader: FC<{ - value: string; - onSelect: (value: string) => void; - data: DataSourceItem; - reportName: string; -}> = ({ value, onSelect, data, reportName }) => { - return ( -
- - - -
- ); -}; - -export default SummaryHeader; diff --git a/packages/istanbul-report-html-modern/src/lib/components/SummaryList.tsx b/packages/istanbul-report-html-modern/src/lib/components/SummaryList.tsx deleted file mode 100644 index c1eed0d..0000000 --- a/packages/istanbul-report-html-modern/src/lib/components/SummaryList.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { ConfigProvider, Progress, Table } from "antd"; -import type { TableColumnsType } from "antd"; -import type { CSSProperties, FC } from "react"; -import Highlighter from "react-highlight-words"; - -import { getColor } from "../helpers/color"; -import type { DataSourceItem } from "../types"; - -const SummaryList: FC<{ - dataSource: DataSourceItem[]; - onSelect: (path: string) => void; - filenameKeywords: string; - style?: CSSProperties; -}> = ({ dataSource, onSelect, filenameKeywords, style }) => { - const columns: TableColumnsType = [ - { - title: "Files", - key: "path", - dataIndex: "path", - render(text: string) { - return ( - { - onSelect(text); - }} - > - - - ); - }, - }, - { - title: "Total", - key: "total", - dataIndex: ["statements", "total"], - sorter: (a, b) => a.statements.total - b.statements.total, - }, - { - title: "Covered", - key: "covered", - dataIndex: ["statements", "covered"], - sorter: (a, b) => a.statements.covered - b.statements.covered, - }, - { - title: "Coverage %", - width: "240px", - key: "c", - sorter: (a, b) => a.statements.pct - b.statements.pct, - dataIndex: ["statements", "pct"], - render(text: number) { - return ( - - ); - }, - }, - ]; - - return ( -
- - - - - ); -}; - -export default SummaryList; diff --git a/packages/istanbul-report-html-modern/src/lib/components/SummaryTree.tsx b/packages/istanbul-report-html-modern/src/lib/components/SummaryTree.tsx deleted file mode 100644 index 48cb212..0000000 --- a/packages/istanbul-report-html-modern/src/lib/components/SummaryTree.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { FileOutlined, FolderFilled } from "@ant-design/icons"; -import { ConfigProvider, Progress, Table } from "antd"; -import type { TableColumnsType } from "antd"; -import type { CSSProperties, FC } from "react"; - -import { getColor } from "../helpers/color"; -import type { DataSourceItem } from "../types"; - -function isSourceFile(path: string): boolean { - return /\.(js|jsx|ts|tsx|mjs|cjs|mts|cts|vue|json|css|scss|less|html|md)$/i.test(path); -} - -const SummaryTree: FC<{ - dataSource: DataSourceItem[]; - onSelect: (path: string) => void; - style?: CSSProperties; -}> = ({ dataSource, onSelect, style }) => { - const columns: TableColumnsType = [ - { - title: "Files", - key: "path", - dataIndex: "path", - render(text: string) { - return ( - { - onSelect(text); - }} - > - {isSourceFile(text) ? ( - - ) : ( - - )} - {text.split("/").at(-1)} - - ); - }, - }, - { - title: "Total", - key: "total", - dataIndex: ["statements", "total"], - sorter: (a, b) => a.statements.total - b.statements.total, - }, - { - title: "Covered", - key: "covered", - dataIndex: ["statements", "covered"], - sorter: (a, b) => a.statements.covered - b.statements.covered, - }, - { - title: "Coverage %", - width: "240px", - key: "c", - dataIndex: ["statements", "pct"], - sorter: (a, b) => a.statements.pct - b.statements.pct, - render(text: number) { - return ( - - ); - }, - }, - ]; - - return ( -
- -
- - - ); -}; - -export default SummaryTree; diff --git a/packages/istanbul-report-html-modern/src/lib/components/TopControl.tsx b/packages/istanbul-report-html-modern/src/lib/components/TopControl.tsx deleted file mode 100644 index 3372bf9..0000000 --- a/packages/istanbul-report-html-modern/src/lib/components/TopControl.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import Icon, { BarsOutlined, MoonOutlined, SunOutlined } from "@ant-design/icons"; -import { Button, Divider, Input, Segmented, Space, Tooltip } from "antd"; -import type { FC } from "react"; - -import TreeViewIcon from "../icons/TreeViewIcon"; -import { useTheme } from "../theme-context"; - -const TopControl: FC<{ - total: number; - showMode: string; - filenameKeywords: string; - onChangeShowMode: (mode: string) => void; - onChangeKeywords: (word: string) => void; -}> = ({ total, showMode, onChangeShowMode, onChangeKeywords, filenameKeywords }) => { - const { theme, toggleTheme } = useTheme(); - - return ( -
-
-
- - { - onChangeShowMode(String(v)); - }} - options={[ - { - label: "Code Tree", - value: "tree", - icon: , - }, - { - label: "File List", - value: "list", - icon: , - }, - ]} - /> - - {total} {"Total Files"} - - -
- - -
- -
- ); -}; - -export default TopControl; diff --git a/packages/istanbul-report-html-modern/src/lib/helpers/color.ts b/packages/istanbul-report-html-modern/src/lib/helpers/color.ts deleted file mode 100644 index 6c244d1..0000000 --- a/packages/istanbul-report-html-modern/src/lib/helpers/color.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function getColor(pct: number): string { - if (pct >= 80) { - return "rgb(33,181,119)"; - } - if (pct >= 50) { - return "rgb(244,176,27)"; - } - return "rgb(245,32,32)"; -} diff --git a/packages/istanbul-report-html-modern/src/lib/icons/TreeViewIcon.tsx b/packages/istanbul-report-html-modern/src/lib/icons/TreeViewIcon.tsx deleted file mode 100644 index 78a7988..0000000 --- a/packages/istanbul-report-html-modern/src/lib/icons/TreeViewIcon.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from "react"; - -export default function TreeViewIcon(props: SVGProps) { - return ( - - - - ); -} diff --git a/packages/istanbul-report-html-modern/src/lib/index.css b/packages/istanbul-report-html-modern/src/lib/index.css deleted file mode 100644 index 57ac1b1..0000000 --- a/packages/istanbul-report-html-modern/src/lib/index.css +++ /dev/null @@ -1,147 +0,0 @@ -:root, -[data-theme="light"] { - color-scheme: light; - --report-bg: #ffffff; - --report-text: rgba(0, 0, 0, 0.88); - --report-link: #0071c2; - --report-line-coverage-bg: #eaeaea; - --report-line-coverage-text: rgba(0, 0, 0, 0.5); - --report-line-hit-covered: rgb(230, 245, 208); - --report-line-hit-uncovered: #f3aeac; - --report-line-hit-neutral: rgb(234, 234, 234); - --report-highlight-bg: rgb(244, 176, 27); - --report-highlight-text: #ffffff; - --report-uncovered-branch-bg: rgba(255, 229, 100, 0.6); - --report-uncovered-bg: #f3aeac; - --report-decoration-bg: #000000; - --report-decoration-text: #ffff00; -} - -[data-theme="dark"] { - color-scheme: dark; - --report-bg: #1e1e1e; - --report-text: rgba(255, 255, 255, 0.85); - --report-link: #0071c2; - --report-line-coverage-bg: #303030; - --report-line-coverage-text: rgba(255, 255, 255, 0.45); - --report-line-hit-covered: rgb(45, 74, 34); - --report-line-hit-uncovered: #5c3534; - --report-line-hit-neutral: #303030; - --report-highlight-bg: rgb(180, 130, 20); - --report-highlight-text: #ffffff; - --report-uncovered-branch-bg: rgba(180, 140, 0, 0.35); - --report-uncovered-bg: #5c3534; - --report-decoration-bg: #434343; - --report-decoration-text: #fadb14; -} - -html, -body, -#root { - margin: 0; - height: 100%; - background: var(--report-bg); - color: var(--report-text); -} - -#app { - height: 100%; -} - -body { - font-family: - system-ui, - -apple-system, - "Segoe UI", - Roboto, - sans-serif; -} - -.istanbul-html-modern .coverage-detail-spin-wrapper { - height: 100%; -} -.istanbul-html-modern .coverage-detail-spin-wrapper > .ant-spin-container { - height: 100%; -} -.istanbul-html-modern { - height: 100%; - display: flex; - flex-direction: column; - padding: 6px; - box-sizing: border-box; - background: var(--report-bg); -} - -.istanbul-html-modern .report-scroll-body { - flex: 1; - min-height: 0; - overflow: auto; -} -.istanbul-html-modern a { - color: var(--report-link); - cursor: pointer; -} -.istanbul-html-modern a:hover { - color: var(--report-link); - opacity: 0.8; - text-decoration: underline; -} -.istanbul-html-modern .line-number { - margin-right: 5px; -} -.istanbul-html-modern .line-number-wrapper { - display: flex; - justify-self: end; -} -.istanbul-html-modern .line-number-wrapper > span { - display: block; -} - -.istanbul-html-modern .line-coverage { - background-color: var(--report-line-coverage-bg); - width: 10px; - padding-right: 5px; - color: var(--report-line-coverage-text); -} - -.istanbul-html-modern .YourHighlightClass { - padding: 0; - color: var(--report-highlight-text); - background: var(--report-highlight-bg); -} - -.istanbul-html-modern .content-class-no-found-branch { - background: var(--report-uncovered-branch-bg); -} - -.istanbul-html-modern .content-class-no-found { - background: var(--report-uncovered-bg); -} - -.istanbul-html-modern .insert-e-decoration::before { - content: "E"; - background-color: var(--report-decoration-bg); - color: var(--report-decoration-text); - padding: 0 2px; - border-radius: 2px; - margin-right: 5px; -} - -.istanbul-html-modern .insert-i-decoration::before { - content: "I"; - background-color: var(--report-decoration-bg); - color: var(--report-decoration-text); - padding: 0 2px; - border-radius: 2px; - margin-right: 5px; -} - -.istanbul-html-modern .coverage-detail-container { - height: 100%; - position: relative; -} - -.istanbul-html-modern .coverage-detail-editor { - width: 100%; - height: 100%; -} diff --git a/packages/istanbul-report-html-modern/src/lib/monaco-esm.d.ts b/packages/istanbul-report-html-modern/src/lib/monaco-esm.d.ts deleted file mode 100644 index dd0045a..0000000 --- a/packages/istanbul-report-html-modern/src/lib/monaco-esm.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare module "monaco-editor/editor/editor.api" { - export * from "monaco-editor"; -} - -declare module "monaco-editor/editor/contrib/hover/browser/hoverContribution" {} -declare module "monaco-editor/languages/definitions/javascript/register" {} -declare module "monaco-editor/languages/definitions/typescript/register" {} diff --git a/packages/istanbul-report-html-modern/src/lib/monaco.ts b/packages/istanbul-report-html-modern/src/lib/monaco.ts deleted file mode 100644 index 8be76e9..0000000 --- a/packages/istanbul-report-html-modern/src/lib/monaco.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as monaco from "monaco-editor/editor/editor.api"; -import "monaco-editor/editor/contrib/hover/browser/hoverContribution"; -import "monaco-editor/languages/definitions/javascript/register"; -import "monaco-editor/languages/definitions/typescript/register"; - -export { monaco }; - -/** Map coverage file paths to Monaco languages (JS/TS only; others use plaintext). */ -export function languageFromPath(filePath: string): string { - const ext = filePath.split(".").pop()?.toLowerCase(); - switch (ext) { - case "ts": - case "mts": - case "cts": - case "tsx": - return "typescript"; - case "js": - case "mjs": - case "cjs": - case "jsx": - return "javascript"; - default: - return "plaintext"; - } -} diff --git a/packages/istanbul-report-html-modern/src/lib/vite-env.d.ts b/packages/istanbul-report-html-modern/src/lib/vite-env.d.ts deleted file mode 100644 index 863901a..0000000 --- a/packages/istanbul-report-html-modern/src/lib/vite-env.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -/// - -declare module "monaco-editor-css" {} diff --git a/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx b/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx index 2ab1639..ae10a50 100644 --- a/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx +++ b/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx @@ -33,7 +33,7 @@ export function ReportFooter({ generatedAt, packageName, packageVersion }: Repor target="_blank" rel="noopener noreferrer" > - istanbul + @vitest/istanbul-report-html-modern {hasPackageInfo ? ( <> diff --git a/packages/istanbul-report-html-modern/src/page/decompress.ts b/packages/istanbul-report-html-modern/src/page/decompress.ts index f9872cf..6ec688b 100644 --- a/packages/istanbul-report-html-modern/src/page/decompress.ts +++ b/packages/istanbul-report-html-modern/src/page/decompress.ts @@ -26,7 +26,6 @@ export async function decompressGzip(compressedData: Uint8Array) { } } - // Merge decompressed chunks into one buffer. const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); const result = new Uint8Array(totalLength); let offset = 0; diff --git a/packages/istanbul-report-html-modern/src/page/load-data.ts b/packages/istanbul-report-html-modern/src/page/load-data.ts index 023c7a9..7f9f2f2 100644 --- a/packages/istanbul-report-html-modern/src/page/load-data.ts +++ b/packages/istanbul-report-html-modern/src/page/load-data.ts @@ -1,5 +1,5 @@ -import { base64ToUint8Array, decompressGzip } from "./decompress.ts"; -import type { ReportData } from "./report-data.ts"; +import { base64ToUint8Array, decompressGzip } from "./decompress"; +import type { ReportData } from "./report-data"; /** * Decode gzip+base64 `window.reportData` (as embedded by the html-modern reporter) diff --git a/packages/istanbul-report-html-modern/src/page/main.tsx b/packages/istanbul-report-html-modern/src/page/main.tsx index 754b78c..836be7c 100644 --- a/packages/istanbul-report-html-modern/src/page/main.tsx +++ b/packages/istanbul-report-html-modern/src/page/main.tsx @@ -1,14 +1,8 @@ -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; -import "monaco-editor-css"; +import { render } from "preact"; -import { loadDevReportData } from "./load-data.ts"; -import ReportShell from "./shell.tsx"; +import { loadDevReportData } from "./load-data"; +import ReportShell from "./shell"; await loadDevReportData(); -createRoot(document.getElementById("root")!).render( - - - , -); +render(, document.getElementById("root")!); diff --git a/packages/istanbul-report-html-modern/src/page/report-data.ts b/packages/istanbul-report-html-modern/src/page/report-data.ts index 3ab13a2..e11d484 100644 --- a/packages/istanbul-report-html-modern/src/page/report-data.ts +++ b/packages/istanbul-report-html-modern/src/page/report-data.ts @@ -1,12 +1,13 @@ -/** Keep in sync with packages/istanbul-lib-report/src/reports/html-modern/coverage-report.ts */ +/** Serialized coverage report payload embedded in the single-file HTML page. */ export interface ReportData { - html: { + html?: { verbose?: boolean; subdir?: string; skipEmpty?: boolean; metricsToShow?: ("lines" | "branches" | "functions" | "statements")[]; + fileTags?: { glob: string; tag: string }[]; }; - istanbul: { + istanbul?: { dir: string; watermarks: { statements: [number, number]; @@ -18,7 +19,7 @@ export interface ReportData { summarizer?: "flat" | "nested" | "pkg" | "defaultSummarizer"; sourceFinder: "filesystem" | "custom"; }; - stats: { + stats?: { coverageFileCount: number; sourceFileCount: number; }; @@ -26,6 +27,8 @@ export interface ReportData { projectRoot: string; coverage: Record; sources: Record; + fileTagRules?: { glob: string; tag: string }[]; + fileTagsByPath?: Record; generatedAt?: string; packageName?: string; packageVersion?: string; diff --git a/packages/istanbul-report-html-modern/src/page/shell.tsx b/packages/istanbul-report-html-modern/src/page/shell.tsx index 55010de..5f3215e 100644 --- a/packages/istanbul-report-html-modern/src/page/shell.tsx +++ b/packages/istanbul-report-html-modern/src/page/shell.tsx @@ -1,7 +1,7 @@ import { buildReportFiles, ReportApp } from "@vitest/istanbul-report-html-modern"; import "@vitest/istanbul-report-html-modern/style.css"; -import { useMemo } from "react"; +import { useMemo } from "preact/hooks"; import { ReportFooter } from "./components/ReportFooter"; import type { ReportData } from "./report-data"; @@ -24,7 +24,14 @@ export function ReportShell() { return (
- +
-declare module "monaco-editor-css" {} - declare module "@repo/fixtures/report-data.json" { - import type { ReportData } from "./report-data.ts"; + import type { ReportData } from "./report-data"; const reportData: ReportData; export default reportData; diff --git a/packages/istanbul-report-html-modern/src/lib/paths.ts b/packages/istanbul-report-html-modern/src/paths.ts similarity index 100% rename from packages/istanbul-report-html-modern/src/lib/paths.ts rename to packages/istanbul-report-html-modern/src/paths.ts diff --git a/packages/istanbul-report-html-modern/src/lib/theme-context.tsx b/packages/istanbul-report-html-modern/src/theme-context.tsx similarity index 87% rename from packages/istanbul-report-html-modern/src/lib/theme-context.tsx rename to packages/istanbul-report-html-modern/src/theme-context.tsx index 7f2caf1..268d37b 100644 --- a/packages/istanbul-report-html-modern/src/lib/theme-context.tsx +++ b/packages/istanbul-report-html-modern/src/theme-context.tsx @@ -1,12 +1,6 @@ -import { - createContext, - useCallback, - useContext, - useEffect, - useState, - type FC, - type ReactNode, -} from "react"; +import { createContext } from "preact"; +import type { ComponentChildren, FunctionalComponent as FC } from "preact"; +import { useCallback, useContext, useEffect, useState } from "preact/hooks"; export type ThemeMode = "light" | "dark"; @@ -47,7 +41,7 @@ interface ThemeContextValue { const ThemeContext = createContext(null); -export const ThemeProvider: FC<{ children: ReactNode }> = ({ children }) => { +export const ThemeProvider: FC<{ children: ComponentChildren }> = ({ children }) => { const [theme, setTheme] = useState(() => resolveTheme()); useEffect(() => { diff --git a/packages/istanbul-report-html-modern/src/lib/types.ts b/packages/istanbul-report-html-modern/src/types.ts similarity index 73% rename from packages/istanbul-report-html-modern/src/lib/types.ts rename to packages/istanbul-report-html-modern/src/types.ts index c0eae6c..c9fa504 100644 --- a/packages/istanbul-report-html-modern/src/lib/types.ts +++ b/packages/istanbul-report-html-modern/src/types.ts @@ -1,3 +1,6 @@ +import type { FileTagRule } from "./file-tags.js"; +import type { StatementWatermarks } from "./helpers/color.js"; + export interface CoverageLocation { line: number; column: number; @@ -40,6 +43,8 @@ export interface CoverageTotals { export interface DataSourceItem { path: string; + /** tags resolved from reporter {@link FileTagRule} config */ + tags?: string[]; statements: CoverageTotals; branches: CoverageTotals; functions: CoverageTotals; @@ -58,6 +63,10 @@ export interface ReportProps { value: string; dataSource: DataSourceItem[]; onSelect: (val: string) => Promise; + /** configured tag rules; when set, enables tag filtering in the UI */ + fileTagRules?: FileTagRule[]; + /** statements coverage color thresholds from istanbul watermarks */ + statementWatermarks?: StatementWatermarks; } export interface ReportAppFile extends FileCoverageData { @@ -71,4 +80,8 @@ export interface ReportAppProps { name?: string; /** Initial path when there is no hash, e.g. `src/index.ts` */ defaultValue?: string; + fileTagRules?: FileTagRule[]; + fileTagsByPath?: Record; + /** statements coverage color thresholds from istanbul watermarks */ + statementWatermarks?: StatementWatermarks; } diff --git a/packages/istanbul-report-html-modern/src/lib/useHashPath.ts b/packages/istanbul-report-html-modern/src/useHashPath.ts similarity index 91% rename from packages/istanbul-report-html-modern/src/lib/useHashPath.ts rename to packages/istanbul-report-html-modern/src/useHashPath.ts index 27b938e..b5083b9 100644 --- a/packages/istanbul-report-html-modern/src/lib/useHashPath.ts +++ b/packages/istanbul-report-html-modern/src/useHashPath.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "preact/hooks"; import { readHashPath, writeHashPath } from "./hash-route"; diff --git a/packages/istanbul-report-html-modern/src/vite-env.d.ts b/packages/istanbul-report-html-modern/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/packages/istanbul-report-html-modern/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/istanbul-report-html-modern/test/color.test.ts b/packages/istanbul-report-html-modern/test/color.test.ts new file mode 100644 index 0000000..1f9bca9 --- /dev/null +++ b/packages/istanbul-report-html-modern/test/color.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; + +import { getColor } from "../src/helpers/color"; + +describe("getColor", () => { + it("uses istanbul default watermarks when none are provided", () => { + expect(getColor(49)).toBe("rgb(245, 32, 32)"); + expect(getColor(50)).toBe("rgb(244, 176, 27)"); + expect(getColor(79)).toBe("rgb(244, 176, 27)"); + expect(getColor(80)).toBe("rgb(33, 181, 119)"); + }); + + it("uses configured statement watermarks", () => { + const watermarks = [80, 95] as const; + + expect(getColor(79, watermarks)).toBe("rgb(245, 32, 32)"); + expect(getColor(80, watermarks)).toBe("rgb(244, 176, 27)"); + expect(getColor(94, watermarks)).toBe("rgb(244, 176, 27)"); + expect(getColor(95, watermarks)).toBe("rgb(33, 181, 119)"); + }); +}); diff --git a/packages/istanbul-report-html-modern/test/paths.test.ts b/packages/istanbul-report-html-modern/test/paths.test.ts index 6c04fe5..2175e50 100644 --- a/packages/istanbul-report-html-modern/test/paths.test.ts +++ b/packages/istanbul-report-html-modern/test/paths.test.ts @@ -1,11 +1,6 @@ import { assert, describe, it } from "vitest"; -import { - projectRootBaseName, - resolveSource, - toAbsolutePath, - toRelativePath, -} from "../src/lib/paths"; +import { projectRootBaseName, resolveSource, toAbsolutePath, toRelativePath } from "../src/paths"; describe("toRelativePath", () => { it("strips the projectRoot prefix", () => { diff --git a/packages/istanbul-report-html-modern/test/summary.test.ts b/packages/istanbul-report-html-modern/test/summary.test.ts index 7aafac2..221407f 100644 --- a/packages/istanbul-report-html-modern/test/summary.test.ts +++ b/packages/istanbul-report-html-modern/test/summary.test.ts @@ -1,15 +1,15 @@ import { assert, describe, it } from "vitest"; -import { buildReportFiles } from "../src/lib/helpers/build-report-files"; -import { deriveSummaryViews } from "../src/lib/helpers/derive-views"; -import { computeLineHits } from "../src/lib/helpers/line-hits"; +import { buildReportFiles } from "../src/helpers/build-report-files"; +import { deriveSummaryViews } from "../src/helpers/derive-views"; +import { computeLineHits } from "../src/helpers/line-hits"; import { buildSummaryTree, fileCoverageToSummary, filesToDataSource, percent, -} from "../src/lib/helpers/summary"; -import type { FileCoverageData } from "../src/lib/types"; +} from "../src/helpers/summary"; +import type { FileCoverageData } from "../src/types"; function makeFile(path: string, statementHits: number[]): FileCoverageData { const statementMap: FileCoverageData["statementMap"] = {}; @@ -66,6 +66,30 @@ describe("buildSummaryTree", () => { }); describe("deriveSummaryViews", () => { + it("filters by any selected tag", () => { + const dataSource = [ + { path: "src/a.ts", tags: ["team-a"], ...fileCoverageToSummary(makeFile("src/a.ts", [1])) }, + { path: "src/b.ts", tags: ["team-b"], ...fileCoverageToSummary(makeFile("src/b.ts", [1])) }, + { + path: "src/c.ts", + tags: ["team-a", "P0"], + ...fileCoverageToSummary(makeFile("src/c.ts", [1])), + }, + ]; + + const views = deriveSummaryViews({ + dataSource, + filenameKeywords: "", + selectedTags: ["team-a", "P0"], + value: "", + }); + + assert.deepEqual( + views.listDataSource.map((item) => item.path), + ["src/a.ts", "src/c.ts"], + ); + }); + it("filters by keywords and current path", () => { const dataSource = [ { path: "src/a.ts", ...fileCoverageToSummary(makeFile("src/a.ts", [1])) }, diff --git a/packages/istanbul-report-html-modern/tsconfig.json b/packages/istanbul-report-html-modern/tsconfig.json index df36b70..aee237b 100644 --- a/packages/istanbul-report-html-modern/tsconfig.json +++ b/packages/istanbul-report-html-modern/tsconfig.json @@ -1,20 +1,33 @@ { - "extends": "../../tsconfig.json", "compilerOptions": { - "jsxImportSource": "react", - "types": ["vite/client"], + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", "moduleResolution": "bundler", + "jsx": "react-jsx", + "jsxImportSource": "preact", + "strict": true, + "skipLibCheck": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "noEmit": true, "allowImportingTsExtensions": true, - "noEmit": true + "types": ["vite/client"], + "paths": { + "@vitest/istanbul-report-html-modern": ["./src/index.ts"], + "@vitest/istanbul-report-html-modern/style.css": ["./src/index.css"] + } }, "include": [ - "src/lib", - "src/page", + "src", + "playground/src", + "test", "vite.config.ts", "vite.config.playground.ts", "vite-plugin-report-data-dev.ts", "report-data-placeholder.ts", - "playground/src" + "tsdown.config.ts", + "vitest.config.ts" ], - "exclude": [] + "exclude": ["dist", "node_modules"] } diff --git a/packages/istanbul-report-html-modern/tsdown.config.ts b/packages/istanbul-report-html-modern/tsdown.config.ts index d04633f..f9ddb69 100644 --- a/packages/istanbul-report-html-modern/tsdown.config.ts +++ b/packages/istanbul-report-html-modern/tsdown.config.ts @@ -1,8 +1,8 @@ import { defineConfig } from "tsdown"; -/** React component library → `dist/index.js` + `dist/style.css` */ +/** Preact component library → `dist/index.js` + `dist/style.css` */ export default defineConfig({ - entry: ["src/lib/index.ts"], + entry: ["src/index.ts"], platform: "neutral", dts: true, exports: true, diff --git a/packages/istanbul-report-html-modern/vite-plugin-report-data-dev.ts b/packages/istanbul-report-html-modern/vite-plugin-report-data-dev.ts index 66eebd2..50c4f75 100644 --- a/packages/istanbul-report-html-modern/vite-plugin-report-data-dev.ts +++ b/packages/istanbul-report-html-modern/vite-plugin-report-data-dev.ts @@ -7,8 +7,7 @@ import type { Plugin } from "vite"; import { REPORT_DATA_PLACEHOLDER } from "./report-data-placeholder.js"; const packageRoot = dirname(fileURLToPath(import.meta.url)); -const repoRoot = join(packageRoot, "../.."); -const mockPath = join(repoRoot, "coverage/report-data.json"); +const mockPath = join(packageRoot, "coverage/report-data.json"); /** In dev, neutralize the production placeholder so inline HTML stays valid JS. */ export function reportDataDevPlugin(): Plugin { @@ -22,7 +21,7 @@ export function reportDataDevPlugin(): Plugin { if (!fs.existsSync(mockPath)) { console.warn( - `[report-data-dev] ${mockPath} not found; run repo tests once to generate it, or use pnpm play`, + `[report-data-dev] ${mockPath} not found; place coverage/report-data.json or use pnpm play`, ); } diff --git a/packages/istanbul-report-html-modern/vite.config.playground.ts b/packages/istanbul-report-html-modern/vite.config.playground.ts index c6b4f5a..5219cf3 100644 --- a/packages/istanbul-report-html-modern/vite.config.playground.ts +++ b/packages/istanbul-report-html-modern/vite.config.playground.ts @@ -1,53 +1,29 @@ -/// -import { createRequire } from "node:module"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import react from "@vitejs/plugin-react"; +import preact from "@preact/preset-vite"; import { defineConfig } from "vite"; -const require = createRequire(import.meta.url); const packageRoot = dirname(fileURLToPath(import.meta.url)); -const repoRoot = join(packageRoot, "../.."); -const monacoCss = join( - dirname(require.resolve("monaco-editor")), - "../../min/vs/editor/editor.main.css", -); /** UI playground (consumes the public library API). */ export default defineConfig({ root: join(packageRoot, "playground"), - plugins: [react()], + plugins: [preact()], resolve: { alias: [ { find: "@vitest/istanbul-report-html-modern/style.css", - replacement: join(packageRoot, "src/lib/index.css"), + replacement: join(packageRoot, "src/index.css"), }, { find: "@vitest/istanbul-report-html-modern", - replacement: join(packageRoot, "src/lib/index.ts"), + replacement: join(packageRoot, "src/index.ts"), }, { find: "@repo/fixtures", - replacement: join(repoRoot, "coverage"), + replacement: join(packageRoot, "coverage"), }, - { - find: "monaco-editor-css", - replacement: monacoCss, - }, - ], - }, - optimizeDeps: { - include: [ - "@ant-design/icons", - "antd", - "monaco-editor/editor/editor.api", - "monaco-editor/editor/contrib/hover/browser/hoverContribution", - "monaco-editor/languages/definitions/javascript/register", - "monaco-editor/languages/definitions/typescript/register", - "react-dom/server", - "react-highlight-words", ], }, server: { diff --git a/packages/istanbul-report-html-modern/vite.config.ts b/packages/istanbul-report-html-modern/vite.config.ts index 3dffd3c..b162034 100644 --- a/packages/istanbul-report-html-modern/vite.config.ts +++ b/packages/istanbul-report-html-modern/vite.config.ts @@ -1,31 +1,24 @@ -import { createRequire } from "node:module"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import react from "@vitejs/plugin-react"; +import preact from "@preact/preset-vite"; import { defineConfig } from "vite"; import { viteSingleFile } from "vite-plugin-singlefile"; import { reportDataDevPlugin } from "./vite-plugin-report-data-dev.js"; -const require = createRequire(import.meta.url); const packageRoot = dirname(fileURLToPath(import.meta.url)); -const repoRoot = join(packageRoot, "../.."); -const monacoCss = join( - dirname(require.resolve("monaco-editor")), - "../../min/vs/editor/editor.main.css", -); function libAliases(mode: string) { const production = mode === "production"; return [ { find: "@vitest/istanbul-report-html-modern/style.css", - replacement: join(packageRoot, production ? "dist/style.css" : "src/lib/index.css"), + replacement: join(packageRoot, production ? "dist/style.css" : "src/index.css"), }, { find: "@vitest/istanbul-report-html-modern", - replacement: join(packageRoot, production ? "dist/index.js" : "src/lib/index.ts"), + replacement: join(packageRoot, production ? "dist/index.js" : "src/index.ts"), }, ] as const; } @@ -33,17 +26,13 @@ function libAliases(mode: string) { /** Single-file HTML report page → `dist/page/index.html` */ export default defineConfig(({ mode }) => ({ root: join(packageRoot, "src/page"), - plugins: [react(), reportDataDevPlugin(), viteSingleFile()], + plugins: [preact(), reportDataDevPlugin(), viteSingleFile()], resolve: { alias: [ ...libAliases(mode), { find: "@repo/fixtures", - replacement: join(repoRoot, "coverage"), - }, - { - find: "monaco-editor-css", - replacement: monacoCss, + replacement: join(packageRoot, "coverage"), }, ], }, diff --git a/packages/istanbul-report-html-modern/vitest.config.ts b/packages/istanbul-report-html-modern/vitest.config.ts index 94fe224..a6006a9 100644 --- a/packages/istanbul-report-html-modern/vitest.config.ts +++ b/packages/istanbul-report-html-modern/vitest.config.ts @@ -1,9 +1,34 @@ -import { defineProject } from "vitest/config"; +import { join } from "node:path"; -import pkg from "./package.json" with { type: "json" }; +import { defineConfig } from "vitest/config"; -export default defineProject({ +const htmlModernReporter = join(process.cwd(), "../../html-modern.cjs"); + +export default defineConfig({ test: { - name: { label: pkg.name, color: "cyan" }, + coverage: { + enabled: true, + reportsDirectory: "./coverage/raw", + reporter: [ + [ + htmlModernReporter, + { + writeReportDataJson: true, + fileTags: [ + { glob: "src/page/**", tag: "page" }, + { glob: "src/components/**", tag: "components" }, + { glob: "src/helpers/**", tag: "helpers" }, + { glob: "src/*.{ts,tsx}", tag: "core" }, + ], + }, + ], + ], + watermarks: { + lines: [80, 95], + functions: [80, 95], + branches: [80, 95], + statements: [80, 95], + }, + }, }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46ca4c5..4d87395 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,15 +6,27 @@ settings: catalogs: default: - '@ant-design/icons': - specifier: ^6.3.2 - version: 6.3.2 '@babel/core': specifier: ^8.0.1 version: 8.0.1 '@babel/parser': specifier: ^8.0.4 version: 8.0.4 + '@codemirror/lang-javascript': + specifier: ^6.2.4 + version: 6.2.5 + '@codemirror/language': + specifier: ^6.11.3 + version: 6.12.4 + '@codemirror/state': + specifier: ^6.5.2 + version: 6.7.4 + '@codemirror/theme-one-dark': + specifier: ^6.1.3 + version: 6.1.3 + '@codemirror/view': + specifier: ^6.38.6 + version: 6.43.11 '@e18e/eslint-plugin': specifier: ^0.8.0 version: 0.8.0 @@ -24,39 +36,30 @@ catalogs: '@jridgewell/trace-mapping': specifier: ^0.3.23 version: 0.3.31 + '@preact/preset-vite': + specifier: ^2.10.2 + version: 2.10.6 '@tsdown/css': specifier: ^0.22.14 version: 0.22.14 '@types/node': specifier: ^26.2.0 version: 26.2.0 - '@types/react': - specifier: ^19.2.18 - version: 19.2.18 - '@types/react-dom': - specifier: ^19.2.4 - version: 19.2.5 - '@types/react-highlight-words': - specifier: ^0.20.1 - version: 0.20.1 - '@vitejs/plugin-react': - specifier: ^6.1.0 - version: 6.1.0 + '@types/picomatch': + specifier: ^4.0.3 + version: 4.0.3 '@vitest/browser-playwright': specifier: ^5.0.0 version: 5.0.0 '@vitest/coverage-v8': specifier: ^5.0.0 version: 5.0.0 - antd: - specifier: ^6.6.1 - version: 6.6.1 changelogithub: specifier: ^15.0.4 version: 15.0.4 - monaco-editor: - specifier: ^0.56.0 - version: 0.56.0 + lucide-preact: + specifier: ^1.28.0 + version: 1.45.0 obug: specifier: ^2.1.4 version: 2.1.4 @@ -66,21 +69,15 @@ catalogs: oxlint: specifier: ^1.79.0 version: 1.79.0 + picomatch: + specifier: ^4.0.7 + version: 4.0.7 playwright: specifier: ^1.62.1 version: 1.62.1 preact: specifier: ^10.29.8 version: 10.29.8 - react: - specifier: ^19.2.8 - version: 19.2.8 - react-dom: - specifier: ^19.2.8 - version: 19.2.8 - react-highlight-words: - specifier: ^0.21.0 - version: 0.21.0 tsdown: specifier: ^0.22.14 version: 0.22.14 @@ -100,6 +97,14 @@ catalogs: specifier: ^2.9.0 version: 2.9.0 +overrides: + '@vitest/coverage-istanbul>@vitest/istanbul-lib-coverage': ./packages/istanbul-lib-coverage + '@vitest/coverage-istanbul>@vitest/istanbul-lib-instrument': ./packages/istanbul-lib-instrument + '@vitest/coverage-istanbul>@vitest/istanbul-lib-report': ./packages/istanbul-lib-report + '@vitest/coverage-istanbul>@vitest/istanbul-lib-source-maps': ./packages/istanbul-lib-source-maps + '@vitest/coverage-v8>@vitest/istanbul-lib-coverage': ./packages/istanbul-lib-coverage + '@vitest/coverage-v8>@vitest/istanbul-lib-report': ./packages/istanbul-lib-report + importers: .: @@ -165,7 +170,13 @@ importers: '@vitest/istanbul-lib-coverage': specifier: workspace:* version: link:../istanbul-lib-coverage + picomatch: + specifier: 'catalog:' + version: 4.0.7 devDependencies: + '@types/picomatch': + specifier: 'catalog:' + version: 4.0.3 '@vitest/browser-playwright': specifier: 'catalog:' version: 5.0.0(playwright@1.62.1)(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0))(vitest@5.0.0) @@ -200,43 +211,49 @@ importers: packages/istanbul-report-html-modern: dependencies: - '@ant-design/icons': + lucide-preact: specifier: 'catalog:' - version: 6.3.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - antd: + version: 1.45.0(preact@10.29.8) + picomatch: + specifier: 'catalog:' + version: 4.0.7 + devDependencies: + '@codemirror/lang-javascript': specifier: 'catalog:' - version: 6.6.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - monaco-editor: + version: 6.2.5 + '@codemirror/language': specifier: 'catalog:' - version: 0.56.0 - react-highlight-words: + version: 6.12.4 + '@codemirror/state': specifier: 'catalog:' - version: 0.21.0(react@19.2.8) - devDependencies: + version: 6.7.4 + '@codemirror/theme-one-dark': + specifier: 'catalog:' + version: 6.1.3 + '@codemirror/view': + specifier: 'catalog:' + version: 6.43.11 + '@preact/preset-vite': + specifier: 'catalog:' + version: 2.10.6(@babel/core@7.29.7)(preact@10.29.8)(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0)) '@tsdown/css': specifier: 'catalog:' version: 0.22.14(jiti@2.7.0)(postcss@8.5.26)(tsdown@0.22.14)(yaml@2.9.0) '@types/node': specifier: 'catalog:' version: 26.2.0 - '@types/react': - specifier: 'catalog:' - version: 19.2.18 - '@types/react-dom': + '@types/picomatch': specifier: 'catalog:' - version: 19.2.5(@types/react@19.2.18) - '@types/react-highlight-words': - specifier: 'catalog:' - version: 0.20.1 - '@vitejs/plugin-react': - specifier: 'catalog:' - version: 6.1.0(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0)) - react: + version: 4.0.3 + '@vitest/coverage-v8': specifier: 'catalog:' - version: 19.2.8 - react-dom: + version: 5.0.0(@vitest/browser@5.0.0)(vitest@5.0.0) + '@vitest/istanbul-lib-report': + specifier: workspace:* + version: link:../istanbul-lib-report + preact: specifier: 'catalog:' - version: 19.2.8(react@19.2.8) + version: 10.29.8 tsdown: specifier: 'catalog:' version: 0.22.14(@tsdown/css@0.22.14)(typescript@7.0.2) @@ -249,68 +266,78 @@ importers: vite-plugin-singlefile: specifier: 'catalog:' version: 2.3.3(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0)) + vitest: + specifier: 'catalog:' + version: 5.0.0(@types/node@26.2.0)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-v8@5.0.0)(@vitest/ui@5.0.0)(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0)) packages: - '@ant-design/colors@8.0.1': - resolution: {integrity: sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==} - - '@ant-design/cssinjs-utils@2.1.2': - resolution: {integrity: sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==} - peerDependencies: - react: '>=18' - react-dom: '>=18' - - '@ant-design/cssinjs@2.1.2': - resolution: {integrity: sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - '@ant-design/fast-color@3.0.1': - resolution: {integrity: sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==} - engines: {node: '>=8.x'} - - '@ant-design/icons-svg@4.5.0': - resolution: {integrity: sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA==} - - '@ant-design/icons@6.3.2': - resolution: {integrity: sha512-B6O5a5XJ4wjtNOfZejXYwHW5zvKV5gYkjGf11dHGLEbKn0ABDGndo41+gfIiXyTFhvESj4XTotuud33mUFid0g==} - engines: {node: '>=8'} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - '@ant-design/react-slick@2.0.0': - resolution: {integrity: sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==} - peerDependencies: - react: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} '@babel/code-frame@8.0.0': resolution: {integrity: sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@8.0.0': resolution: {integrity: sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + '@babel/core@8.0.1': resolution: {integrity: sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + '@babel/generator@8.0.0': resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@8.0.0': resolution: {integrity: sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + '@babel/helper-globals@8.0.0': resolution: {integrity: sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} @@ -327,10 +354,18 @@ packages: resolution: {integrity: sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@8.0.0': resolution: {integrity: sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + '@babel/helpers@8.0.0': resolution: {integrity: sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==} engines: {node: ^22.18.0 || >=24.11.0} @@ -345,17 +380,36 @@ packages: engines: {node: ^22.18.0 || >=24.11.0} hasBin: true - '@babel/runtime@7.29.7': - resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.29.7': + resolution: {integrity: sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.29.7': + resolution: {integrity: sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@babel/runtime@8.0.0': - resolution: {integrity: sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} '@babel/template@8.0.0': resolution: {integrity: sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + '@babel/traverse@8.0.4': resolution: {integrity: sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==} engines: {node: ^22.18.0 || >=24.11.0} @@ -375,6 +429,27 @@ packages: '@blazediff/core@1.10.0': resolution: {integrity: sha512-AOQff0zgR7cGsZL+4E7hVkmujoPUpm0J9xzWGWZj5wCjd3gmxESXAPfKyuzs93VdpQNFhHlBhfOjrcZ+XTERtQ==} + '@codemirror/autocomplete@6.20.3': + resolution: {integrity: sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==} + + '@codemirror/lang-javascript@6.2.5': + resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==} + + '@codemirror/language@6.12.4': + resolution: {integrity: sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==} + + '@codemirror/lint@6.9.7': + resolution: {integrity: sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==} + + '@codemirror/state@6.7.4': + resolution: {integrity: sha512-QhQIVRY+xHZDxwOSFrJ1eUMapJBUID3IdeAjf7dHO7zBUzSkyooHiodnalz5MG3iHzwixKMlAAyn7244y537EA==} + + '@codemirror/theme-one-dark@6.1.3': + resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==} + + '@codemirror/view@6.43.11': + resolution: {integrity: sha512-2+esucbQX6wB2JYi1eDvdCPFTA31BN8oSy6xCmk3G6CloV11yOvEjYk+gH7kLrP0MuHG94E8WDhjs5oMiu3+Wg==} + '@e18e/eslint-plugin@0.8.0': resolution: {integrity: sha512-js/TeM+XJyoJ2Zk4if5uTEchv3MEbqugc9gLgq8YdB6Dy+LvwGACpQiAwcul0r4LUl0TvhuzAKUeIUPtGp2cew==} peerDependencies: @@ -386,12 +461,6 @@ packages: oxlint: optional: true - '@emotion/hash@0.8.0': - resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} - - '@emotion/unitless@0.7.5': - resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} - '@istanbuljs/schema@0.1.6': resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} engines: {node: '>=8'} @@ -399,6 +468,9 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -412,6 +484,21 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@lezer/common@1.5.2': + resolution: {integrity: sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==} + + '@lezer/highlight@1.2.3': + resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} + + '@lezer/javascript@1.5.4': + resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==} + + '@lezer/lr@1.4.10': + resolution: {integrity: sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==} + + '@marijn/find-cluster-break@1.0.4': + resolution: {integrity: sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==} + '@oxc-project/types@0.146.0': resolution: {integrity: sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==} @@ -662,291 +749,33 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@quansync/fs@1.0.0': - resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} - - '@rc-component/async-validator@6.0.0': - resolution: {integrity: sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA==} - engines: {node: '>=14.x'} - - '@rc-component/cascader@1.22.0': - resolution: {integrity: sha512-SffrA57aS9oub3VuI7ajPhJTPtaNxngSvtRhD40Rd8dwJ5vfWPSrVanWgeepdWFGBt7EHftIK5RUU0u3rCTwWw==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/checkbox@2.0.0': - resolution: {integrity: sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/collapse@1.2.0': - resolution: {integrity: sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/color-picker@3.1.1': - resolution: {integrity: sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/context@2.0.2': - resolution: {integrity: sha512-uiGpAlblCNlziHPwj4S4Iy/oemeuz/hR03mbiEjTCXwsqOIN3BOzsRMyDwpyO5Fm0vIEEJRUf9ZtbRLbhksuTA==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/dialog@1.10.0': - resolution: {integrity: sha512-eDukNlz9vNszAGv7i3zKXdxEd3wgVmNxuJijYt8zvTh17QwTu8KK/bdURRd/lU4qaMzhO1HKKmMrwOnkaw0BvQ==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/drawer@1.4.2': - resolution: {integrity: sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/dropdown@1.0.3': - resolution: {integrity: sha512-YTST/N6kpqpDz3IMuM/PSSZnrDpSOA6dgHv12gPA90ZTSLv2CoqkZ0+9NtwTY6BeO7dstPblSic2QJg7dSFy/g==} - peerDependencies: - react: '>=16.11.0' - react-dom: '>=16.11.0' - - '@rc-component/form@1.8.6': - resolution: {integrity: sha512-1EXVsSKPZC6kGrIqxVck7kAWM6T65b6et/n5wIcyiaIa41VrDshD0nVLHoBDEIZr2ATQsOP6icb4XQkNFMLBAw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/image@1.10.0': - resolution: {integrity: sha512-BjeZCRQ+hw+4WAhvrw8rJvy5fckA2xpf/X2XQEOABUHvLTNB9inB98X3Mp54jYQ7g10DfWERQWHXeC4ylxp1Uw==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/input-number@1.6.2': - resolution: {integrity: sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/input@1.3.1': - resolution: {integrity: sha512-iFvTUT9W+JC/MSin2aGAk8NqsVlTzcExNC9DZariON1IWirju9NoNeEk47an4Q8iHazkoVI/y1LnDi88+CPcig==} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - '@rc-component/listy@1.2.3': - resolution: {integrity: sha512-IXiMjV5s0rczLBlfh7G5nB4M3365mrEeedjwKtf5I+Ns3PqRUsebR2h5u8CeFarsVfLUPC2I5p0h09TNoOWyvQ==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/mentions@1.11.0': - resolution: {integrity: sha512-IC2qXuEBMFHxPIXEFfYWj6Sr7UiDZnOqJHCYQBbwPzopBJOPZIR6mV9U4QH1bYQRlKYlYnIsajWDMgVGgWQyWQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/menu@1.4.1': - resolution: {integrity: sha512-3GsVRoQ4cnF/AoIQ4P+Z1haBfgfBPQfLT1RJY3Nu4DzOnheTslfCiGSPj7bv/cLj5sW5pHqN25dDXGP3JELAlQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/mini-decimal@1.1.4': - resolution: {integrity: sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w==} - engines: {node: '>=8.x'} - - '@rc-component/motion@1.3.3': - resolution: {integrity: sha512-Xh3IszxvlSv3/PLYFyC2UZi9LNB83yOnkB/LNmRzaypZLvkhqUIPS7MQpGZcCMWrNsXV2p6YTSWbSGvFpEle9A==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/mutate-observer@2.0.1': - resolution: {integrity: sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/notification@2.0.7': - resolution: {integrity: sha512-nqZzpf6BPdaj+3ILx7si79LLmqPKyUmQoXa+/9gg0SkH0v1DbD66oJgRMSBEVnd/zUT3D4gwxWIHUKebYf2ZXQ==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/overflow@1.0.1': - resolution: {integrity: sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/pagination@1.4.0': - resolution: {integrity: sha512-CW1g7P9V8u+e8JQdUsl2RWg+GCsoee0mtJjZUCCxn/vb3jzOwDKm6hAdwddHCVBfWJ58eGUBZz3IvnU8rRktjw==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/picker@1.12.0': - resolution: {integrity: sha512-0FZGgDiDZFMm2hcfGv4jyjm7yWIj2MiwfeTzLL0vWkO+8cCSKdiM9XhWpkn1aAsoI2EwUy48Oz2cGfvL8ZKrfw==} - engines: {node: '>=12.x'} - peerDependencies: - date-fns: '>= 2.x' - dayjs: '>= 1.x' - luxon: '>= 3.x' - moment: '>= 2.x' - react: '>=16.9.0' - react-dom: '>=16.9.0' - peerDependenciesMeta: - date-fns: - optional: true - dayjs: - optional: true - luxon: - optional: true - moment: - optional: true - - '@rc-component/portal@2.2.1': - resolution: {integrity: sha512-ck+r1kW/JSv0wxPji3KN2ss9K6Z0qqwusw/mf/0JobXhZ8hC2ejZwCJObW/SvDi0uhA0VzmCnx0CaCci95tcmA==} - engines: {node: '>=12.x'} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/progress@1.0.2': - resolution: {integrity: sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/qrcode@2.0.0': - resolution: {integrity: sha512-aAv3QhPP1xyafuTZOxub6a54pCeBnN3IwQkpETrBtthq4BL5IgxnCbuoBWPDpdLw1y1j6BgBUCAKV92+yX06Dw==} - engines: {node: '>=8.x'} + '@preact/preset-vite@2.10.6': + resolution: {integrity: sha512-ZZ5RIT2ZVXg8UxRA8VZpoT8CdpDG7RFIqOT4bELqNBp85+HKvQBbgmh3gsoW1UOQXx26TLe+ZRNKI7q281Kckw==} peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' + '@babel/core': 7.x + vite: 2.x || 3.x || 4.x || 5.x || 6.x || 7.x || 8.x - '@rc-component/rate@1.0.1': - resolution: {integrity: sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==} - engines: {node: '>=8.x'} + '@prefresh/babel-plugin@0.5.4': + resolution: {integrity: sha512-GSwqHGVem7AjnOKAkmLNbAExqZZFddf9S3ySOwowGEsJR8vwNKJ7Z/262fov5wra8LkDVSmFw3CjMGRdLoCmRA==} peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' + '@babel/core': ^7.0.0 || ^8.0.0 - '@rc-component/resize-observer@1.1.2': - resolution: {integrity: sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==} + '@prefresh/core@1.5.11': + resolution: {integrity: sha512-Ml00PP8jeHaADxfQqxUs4+M5JS+T5J26UnvPVfsrmlpGw2I3e9XtM0DMeuzkmvg+Tn7VdHSMpLBrDPG0iHoDqQ==} peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' + preact: ^10.0.0 || ^11.0.0-0 - '@rc-component/segmented@1.3.0': - resolution: {integrity: sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - '@rc-component/select@1.10.1': - resolution: {integrity: sha512-H+yQsl+qED9NilQ3g6zdpsMwUgwVjrcMTkNHAWRVU/MoNCYgTbDgU+MIMgZDK+rVdd2JUfI/MkysMcZZ0cyQKw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '*' - react-dom: '*' - - '@rc-component/slider@1.1.1': - resolution: {integrity: sha512-LSzgWGYDgeCDgR4r1XlU29gbYws6HpLnvJd/uMhLeW/vQgxldeR+Wb4uzHDCHiYEbr1bnEHWdjkPxjJRHxuiig==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/steps@1.2.2': - resolution: {integrity: sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/switch@1.0.3': - resolution: {integrity: sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/table@1.11.1': - resolution: {integrity: sha512-OWdS6DMmeWb7bJBGqPxYZpQbzBlBiXZUu2sqo6Ii7Sjs9GeK1IsrXrWk26SL2c6KEseabswdxrRj7WUm9LdECw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/tabs@1.12.0': - resolution: {integrity: sha512-XL7Kqy5fnUE2WTlO1/fCGrrfNlGFebdr7JseGkEIjzcVMAtIFQJ8sqCSOmxcXstjU6fonD/4rnhZHxj7sDTajQ==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/tooltip@1.5.0': - resolution: {integrity: sha512-agQ/+mBqrEQfTX4D3KhQ7j+ZbX4/VHjoJ7Noa2wIdZ1/FbQTOd7Sn92rp+jtCoqAVTLUgSOydePIgZ204gi2EQ==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' + '@prefresh/utils@1.2.1': + resolution: {integrity: sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==} - '@rc-component/tour@2.4.0': - resolution: {integrity: sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg==} - engines: {node: '>=8.x'} + '@prefresh/vite@2.4.12': + resolution: {integrity: sha512-FY1fzXpUjiuosznMV0YM7XAOPZjB5FIdWS0W24+XnlxYkt9hNAwwsiKYn+cuTEoMtD/ZVazS5QVssBr9YhpCQA==} peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' + preact: ^10.4.0 || ^11.0.0-0 + vite: '>=2.0.0' - '@rc-component/tree-select@1.16.1': - resolution: {integrity: sha512-a1Oi6EJhqAhdOxxupdJi6fP0RPHMKn5TcfkX2+llaQ4lF4nwfH7b6SCHcnsybaa2s+pk1yZYwVyeOYkDnEBRdg==} - peerDependencies: - react: '*' - react-dom: '*' - - '@rc-component/tree@1.4.0': - resolution: {integrity: sha512-dGsJGDJQedA0BqqVgj3F8BvHXTSZijyhTXdbAdkcx8lynzZkty/CV3Z3LOm/fxz+BCfl3dfGiAQpb7Q5XNvl0Q==} - engines: {node: '>=10.x'} - peerDependencies: - react: '*' - react-dom: '*' - - '@rc-component/trigger@3.10.1': - resolution: {integrity: sha512-mXlDN0IXdtV8Yqqm8195ECCyrbmfvvfKvwVvSlH0+qvKD6BUF8gRhEjSy0FOcD1+CcDRHgTiX99LoxfQrmh3Cw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/upload@1.1.1': - resolution: {integrity: sha512-GvYWSKeaJTOxxC5p6+nOSadzfvXA1h8C/iHFPFZX+szH3JUXrvs+DLiW8YUTBgvMh8m63mJeHrlYlJzAlg+pDA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/util@1.12.0': - resolution: {integrity: sha512-AEjPL8JVdohIITaiXokyjL9WQ6tKWWjAYK9QU16tGNE9JaQABBQy+hA4H2Lup5MgXy9yY3iLrbZJheuU13hTdQ==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/virtual-list@1.5.1': - resolution: {integrity: sha512-boqHxdtyWC88u8quYgEO49bcBy5fzRiOcnBge+N4nLzs2k8hUQ/yw7JE9dM6yCBE4jSm5YSHVCVMS+suBuJGKA==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} '@rolldown/binding-android-arm-eabi@1.2.5': resolution: {integrity: sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==} @@ -1047,6 +876,19 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rollup/pluginutils@4.2.1': + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@tsdown/css@0.22.14': resolution: {integrity: sha512-0NpbgqfhfjaAoLdY3DxjrMo08vKFEtvlybCS42Gtolx8OPoKUdZZDyls0qgNEqea0c7XGjlF0NIE1tIxgU8RqA==} engines: {node: ^22.18.0 || >=24.11.0} @@ -1087,19 +929,8 @@ packages: '@types/node@26.2.0': resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==} - '@types/react-dom@19.2.5': - resolution: {integrity: sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==} - peerDependencies: - '@types/react': ^19.2.0 - - '@types/react-highlight-words@0.20.1': - resolution: {integrity: sha512-EgF6RaoibBurIhxk3X3d5xL0uMqjD7KtShvZ8S+d7/o6xZ+mQ4rct1a2E49vWA9r63OzrHCIyghqvQHASb/ERw==} - - '@types/react@19.2.18': - resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} - - '@types/trusted-types@2.0.7': - resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/picomatch@4.0.3': + resolution: {integrity: sha512-iG0T6+nYJ9FAPmx9SsUlnwcq1ZVRuCXcVEvWnntoPlrOpwtSTKNDC9uVAxTsC3PUvJ+99n4RpAcNgBbHX3JSnQ==} '@typescript/typescript-aix-ppc64@7.0.2': resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} @@ -1221,22 +1052,6 @@ packages: cpu: [x64] os: [win32] - '@vitejs/plugin-react@6.1.0': - resolution: {integrity: sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 - babel-plugin-react-compiler: ^1.0.0 - oxc-transform-react: ^0.145.0 - vite: ^8.0.0 - peerDependenciesMeta: - '@rolldown/plugin-babel': - optional: true - babel-plugin-react-compiler: - optional: true - oxc-transform-react: - optional: true - '@vitest/browser-playwright@5.0.0': resolution: {integrity: sha512-N+gED9y4/8pypaHjz/x0ah3CjoBr+N0hWWT+Gq4VXtMzyB+rUIdHni0ulzpD1j4H77iWy5Jg8PRMlVn6kjxo6g==} peerDependencies: @@ -1257,14 +1072,6 @@ packages: '@vitest/browser': optional: true - '@vitest/istanbul-lib-coverage@1.0.1': - resolution: {integrity: sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==} - engines: {node: '>=22'} - - '@vitest/istanbul-lib-report@1.0.1': - resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==} - engines: {node: '>=22'} - '@vitest/mocker@5.0.0': resolution: {integrity: sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==} peerDependencies: @@ -1434,12 +1241,6 @@ packages: resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} - antd@6.6.1: - resolution: {integrity: sha512-QHIHYoUk9N9nJy1T9fyxWKjY0qApdTEDd/6lzqYng8Uryv9FejNmbhKvYF7obGqB+TuLXQsPVF7fOVgyzM1KrQ==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1451,6 +1252,11 @@ packages: ast-v8-to-istanbul@1.0.5: resolution: {integrity: sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==} + babel-plugin-transform-hook-names@1.0.2: + resolution: {integrity: sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==} + peerDependencies: + '@babel/core': ^7.12.10 + baseline-browser-mapping@2.11.15: resolution: {integrity: sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==} engines: {node: '>=6.0.0'} @@ -1460,6 +1266,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1524,16 +1333,9 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - compute-scroll-into-view@3.1.1: - resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} - confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -1550,11 +1352,24 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + crelt@1.0.7: + resolution: {integrity: sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} - dayjs@1.11.23: - resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==} + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true default-browser-id@5.0.1: resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} @@ -1578,8 +1393,18 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - dompurify@3.4.8: - resolution: {integrity: sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ==} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} @@ -1605,6 +1430,10 @@ packages: resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} engines: {node: '>=14'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + es-module-lexer@2.3.2: resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} @@ -1612,6 +1441,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1675,8 +1507,9 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - highlight-words-core@1.2.3: - resolution: {integrity: sha512-m1O9HW3/GNHxzSIXWw1wCNXXsgLlxrP0OI6+ycGUhiUHkikqW3OrwVHz+lxeNBe5yqLESdIcj8PowHQ2zLvUvQ==} + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true hookable@6.1.1: resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} @@ -1710,9 +1543,6 @@ packages: engines: {node: '>=14.16'} hasBin: true - is-mobile@5.0.0: - resolution: {integrity: sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1732,19 +1562,22 @@ packages: js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true - json2mq@0.2.0: - resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + lightningcss-android-arm64@1.33.0: resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} engines: {node: '>= 12.0.0'} @@ -1827,20 +1660,23 @@ packages: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-preact@1.45.0: + resolution: {integrity: sha512-/6/A5P8Wx3NY+Y2Mqg5Cx8Ak50eZyoXiG0ksqGZL7Oo4EXjGX406tWoOH5j+ivktmerzS8ZoFW5EtK7pPuPQ/Q==} + peerDependencies: + preact: ^10.27.2 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.3.1: resolution: {integrity: sha512-rm91zr2Ou+XueDTohjQQjdQEcYM6zVi8KVUCG8Ec3vHwUEKrhSdCNyfuIywkA6hcCAteIn0ZOtAHA6eGpiX+Pg==} magicast@0.5.4: resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} - marked@14.0.0: - resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} - engines: {node: '>= 18'} - hasBin: true - - memoize-one@4.0.3: - resolution: {integrity: sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -1868,9 +1704,6 @@ packages: module-replacements@3.2.0: resolution: {integrity: sha512-zaDcWtW+kLmYGWCMWAyQ8Je333BYmsBMB2X6q5MKBCI697l7OawNaC5TQqfgPVxhdXBA1AjumQjPMcHyJe1f2A==} - monaco-editor@0.56.0: - resolution: {integrity: sha512-sXboRm3BeBeLm938eaiyLMe0OxzfXIlZvbv4ir/jVgQy1zDhWjgmny0WoN45fuDKhCCQsYMbBJrv/A6jd8aCUg==} - mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -1879,6 +1712,9 @@ packages: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nanoid@3.3.18: resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1887,6 +1723,9 @@ packages: node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-html-parser@6.1.13: + resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} + node-releases@2.0.53: resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} engines: {node: '>=18'} @@ -1895,6 +1734,9 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nypm@0.5.4: resolution: {integrity: sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==} engines: {node: ^14.16.0 || >=16.10.0} @@ -2029,23 +1871,6 @@ packages: rc9@3.0.1: resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} - react-dom@19.2.8: - resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} - peerDependencies: - react: ^19.2.8 - - react-highlight-words@0.21.0: - resolution: {integrity: sha512-SdWEeU9fIINArEPO1rO5OxPyuhdEKZQhHzZZP1ie6UeXQf+CjycT1kWaB+9bwGcVbR0NowuHK3RqgqNg6bgBDQ==} - peerDependencies: - react: ^0.14.0 || ^15.0.0 || ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0 - - react-is@19.2.8: - resolution: {integrity: sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==} - - react@19.2.8: - resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} - engines: {node: '>=0.10.0'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2085,15 +1910,13 @@ packages: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - - scroll-into-view-if-needed@3.1.0: - resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} - scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} @@ -2102,6 +1925,9 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + simple-code-frame@1.3.0: + resolution: {integrity: sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==} + sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} @@ -2110,6 +1936,14 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + stack-trace@1.0.0: + resolution: {integrity: sha512-H6D7134xi6qONvh7ZHKgviXf+rd3vhGBSvebPZCaUkd8zvQ+7PtDw6CljPTe4cXWNf2IKZGNqw6VJXSb9IgBpA==} + engines: {node: '>=20.0.0'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -2119,21 +1953,14 @@ packages: std-env@4.2.0: resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} - string-convert@0.2.1: - resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} - - stylis@4.4.0: - resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + style-mod@4.1.3: + resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - throttle-debounce@5.0.2: - resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} - engines: {node: '>=12.22'} - tinybench@6.1.4: resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==} engines: {node: '>=20.0.0'} @@ -2237,6 +2064,11 @@ packages: rollup: optional: true + vite-prerender-plugin@0.5.13: + resolution: {integrity: sha512-IKSpYkzDBsKAxa05naRbj7GvNVMSdww/Z/E89oO3xndz+gWnOBOKOAbEXv7qDhktY/j3vHgJmoV1pPzqU2tx9g==} + peerDependencies: + vite: 5.x || 6.x || 7.x || 8.x + vite@8.2.2: resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2321,6 +2153,9 @@ packages: jsdom: optional: true + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -2342,6 +2177,9 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -2359,61 +2197,46 @@ packages: yuku-parser@0.8.7: resolution: {integrity: sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ==} -snapshots: - - '@ant-design/colors@8.0.1': - dependencies: - '@ant-design/fast-color': 3.0.1 - - '@ant-design/cssinjs-utils@2.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@ant-design/cssinjs': 2.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@babel/runtime': 7.29.7 - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@ant-design/cssinjs@2.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@babel/runtime': 7.29.7 - '@emotion/hash': 0.8.0 - '@emotion/unitless': 0.7.5 - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - csstype: 3.2.3 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - stylis: 4.4.0 - - '@ant-design/fast-color@3.0.1': {} - - '@ant-design/icons-svg@4.5.0': {} + zimmerframe@1.1.5: + resolution: {integrity: sha512-msJxIvYDYcoNL+PJsu+7qmpDWsYmAxTY+2TNYXXF0hzBzBk0BMecOqDOG/EckUoKCuKwObfbugIl8QpqHDXeFA==} - '@ant-design/icons@6.3.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@ant-design/colors': 8.0.1 - '@ant-design/icons-svg': 4.5.0 - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) +snapshots: - '@ant-design/react-slick@2.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@babel/code-frame@7.29.7': dependencies: - '@babel/runtime': 7.29.7 - clsx: 2.1.1 - json2mq: 0.2.0 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - throttle-debounce: 5.0.2 + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 '@babel/code-frame@8.0.0': dependencies: '@babel/helper-validator-identifier': 8.0.4 js-tokens: 10.0.0 + '@babel/compat-data@7.29.7': {} + '@babel/compat-data@8.0.0': {} + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/core@8.0.1': dependencies: '@babel/code-frame': 8.0.0 @@ -2433,6 +2256,14 @@ snapshots: obug: 2.1.4 semver: 7.8.5 + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/generator@8.0.0': dependencies: '@babel/parser': 8.0.4 @@ -2442,6 +2273,18 @@ snapshots: '@types/jsesc': 2.5.1 jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.8 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-compilation-targets@8.0.0': dependencies: '@babel/compat-data': 8.0.0 @@ -2450,8 +2293,28 @@ snapshots: lru-cache: 11.5.2 semver: 7.8.5 + '@babel/helper-globals@7.29.7': {} + '@babel/helper-globals@8.0.0': {} + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + '@babel/helper-string-parser@7.29.7': {} '@babel/helper-string-parser@8.0.0': {} @@ -2460,8 +2323,15 @@ snapshots: '@babel/helper-validator-identifier@8.0.4': {} + '@babel/helper-validator-option@7.29.7': {} + '@babel/helper-validator-option@8.0.0': {} + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + '@babel/helpers@8.0.0': dependencies: '@babel/template': 8.0.0 @@ -2475,9 +2345,34 @@ snapshots: dependencies: '@babel/types': 8.0.4 - '@babel/runtime@7.29.7': {} + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/runtime@8.0.0': {} + '@babel/plugin-transform-react-jsx-development@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 '@babel/template@8.0.0': dependencies: @@ -2485,6 +2380,18 @@ snapshots: '@babel/parser': 8.0.4 '@babel/types': 8.0.4 + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/traverse@8.0.4': dependencies: '@babel/code-frame': 8.0.0 @@ -2509,6 +2416,56 @@ snapshots: '@blazediff/core@1.10.0': {} + '@codemirror/autocomplete@6.20.3': + dependencies: + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.4 + '@codemirror/view': 6.43.11 + '@lezer/common': 1.5.2 + + '@codemirror/lang-javascript@6.2.5': + dependencies: + '@codemirror/autocomplete': 6.20.3 + '@codemirror/language': 6.12.4 + '@codemirror/lint': 6.9.7 + '@codemirror/state': 6.7.4 + '@codemirror/view': 6.43.11 + '@lezer/common': 1.5.2 + '@lezer/javascript': 1.5.4 + + '@codemirror/language@6.12.4': + dependencies: + '@codemirror/state': 6.7.4 + '@codemirror/view': 6.43.11 + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + style-mod: 4.1.3 + + '@codemirror/lint@6.9.7': + dependencies: + '@codemirror/state': 6.7.4 + '@codemirror/view': 6.43.11 + crelt: 1.0.7 + + '@codemirror/state@6.7.4': + dependencies: + '@marijn/find-cluster-break': 1.0.4 + + '@codemirror/theme-one-dark@6.1.3': + dependencies: + '@codemirror/language': 6.12.4 + '@codemirror/state': 6.7.4 + '@codemirror/view': 6.43.11 + '@lezer/highlight': 1.2.3 + + '@codemirror/view@6.43.11': + dependencies: + '@codemirror/state': 6.7.4 + crelt: 1.0.7 + style-mod: 4.1.3 + w3c-keyname: 2.2.8 + '@e18e/eslint-plugin@0.8.0(oxlint@1.79.0)': dependencies: empathic: 2.0.1 @@ -2517,15 +2474,16 @@ snapshots: optionalDependencies: oxlint: 1.79.0 - '@emotion/hash@0.8.0': {} - - '@emotion/unitless@0.7.5': {} - '@istanbuljs/schema@0.1.6': {} '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} @@ -2539,6 +2497,24 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@lezer/common@1.5.2': {} + + '@lezer/highlight@1.2.3': + dependencies: + '@lezer/common': 1.5.2 + + '@lezer/javascript@1.5.4': + dependencies: + '@lezer/common': 1.5.2 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.10 + + '@lezer/lr@1.4.10': + dependencies: + '@lezer/common': 1.5.2 + + '@marijn/find-cluster-break@1.0.4': {} + '@oxc-project/types@0.146.0': {} '@oxfmt/binding-android-arm-eabi@0.64.0': @@ -2657,357 +2633,50 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@quansync/fs@1.0.0': - dependencies: - quansync: 1.0.0 - - '@rc-component/async-validator@6.0.0': - dependencies: - '@babel/runtime': 7.29.7 - - '@rc-component/cascader@1.22.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/select': 1.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/tree': 1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/checkbox@2.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/collapse@1.2.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/color-picker@3.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@ant-design/fast-color': 3.0.1 - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/context@2.0.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/dialog@1.10.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/portal': 2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/drawer@1.4.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/portal': 2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/dropdown@1.0.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/form@1.8.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/async-validator': 6.0.0 - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/image@1.10.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/portal': 2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/input-number@1.6.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/mini-decimal': 1.1.4 - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/input@1.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/listy@1.2.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/portal': 2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/virtual-list': 1.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/mentions@1.11.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/input': 1.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/menu': 1.4.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/menu@1.4.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/overflow': 1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/mini-decimal@1.1.4': - dependencies: - '@babel/runtime': 7.29.7 - - '@rc-component/motion@1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/mutate-observer@2.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/notification@2.0.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/overflow@1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/pagination@1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/picker@1.12.0(dayjs@1.11.23)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/overflow': 1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - optionalDependencies: - dayjs: 1.11.23 - - '@rc-component/portal@2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/progress@1.0.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/qrcode@2.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@babel/runtime': 7.29.7 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/rate@1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/resize-observer@1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/segmented@1.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@preact/preset-vite@2.10.6(@babel/core@7.29.7)(preact@10.29.8)(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0))': dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/select@1.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/overflow': 1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/virtual-list': 1.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/slider@1.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/steps@1.2.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7) + '@prefresh/vite': 2.4.12(preact@10.29.8)(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0)) + '@rollup/pluginutils': 5.4.0 + babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.7) + debug: 4.4.3 + magic-string: 0.30.21 + picocolors: 1.1.1 + vite: 8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0) + vite-prerender-plugin: 0.5.13(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0)) + zimmerframe: 1.1.5 + transitivePeerDependencies: + - preact + - rollup + - supports-color - '@rc-component/switch@1.0.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@prefresh/babel-plugin@0.5.4(@babel/core@7.29.7)': dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@babel/core': 7.29.7 - '@rc-component/table@1.11.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@prefresh/core@1.5.11(preact@10.29.8)': dependencies: - '@rc-component/context': 2.0.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/virtual-list': 1.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + preact: 10.29.8 - '@rc-component/tabs@1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/dropdown': 1.0.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/menu': 1.4.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@prefresh/utils@1.2.1': {} - '@rc-component/tooltip@1.5.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@prefresh/vite@2.4.12(preact@10.29.8)(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0))': dependencies: - '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@babel/core': 7.29.7 + '@prefresh/babel-plugin': 0.5.4(@babel/core@7.29.7) + '@prefresh/core': 1.5.11(preact@10.29.8) + '@prefresh/utils': 1.2.1 + '@rollup/pluginutils': 4.2.1 + preact: 10.29.8 + vite: 8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0) + transitivePeerDependencies: + - supports-color - '@rc-component/tour@2.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/portal': 2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/tree-select@1.16.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@quansync/fs@1.0.0': dependencies: - '@rc-component/select': 1.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/tree': 1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/tree@1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/virtual-list': 1.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/trigger@3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/portal': 2.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/upload@1.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - - '@rc-component/util@1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - is-mobile: 5.0.0 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - react-is: 19.2.8 - - '@rc-component/virtual-list@1.5.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@babel/runtime': 8.0.0 - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + quansync: 1.0.0 '@rolldown/binding-android-arm-eabi@1.2.5': optional: true @@ -3056,6 +2725,17 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} + '@rollup/pluginutils@4.2.1': + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.2 + + '@rollup/pluginutils@5.4.0': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.7 + '@tsdown/css@0.22.14(jiti@2.7.0)(postcss@8.5.26)(tsdown@0.22.14)(yaml@2.9.0)': dependencies: lightningcss: 1.33.0 @@ -3086,20 +2766,7 @@ snapshots: dependencies: undici-types: 8.3.0 - '@types/react-dom@19.2.5(@types/react@19.2.18)': - dependencies: - '@types/react': 19.2.18 - - '@types/react-highlight-words@0.20.1': - dependencies: - '@types/react': 19.2.18 - - '@types/react@19.2.18': - dependencies: - csstype: 3.2.3 - - '@types/trusted-types@2.0.7': - optional: true + '@types/picomatch@4.0.3': {} '@typescript/typescript-aix-ppc64@7.0.2': optional: true @@ -3161,11 +2828,6 @@ snapshots: '@typescript/typescript-win32-x64@7.0.2': optional: true - '@vitejs/plugin-react@6.1.0(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0))': - dependencies: - '@rolldown/pluginutils': 1.0.1 - vite: 8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0) - '@vitest/browser-playwright@5.0.0(playwright@1.62.1)(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0))(vitest@5.0.0)': dependencies: '@vitest/browser': 5.0.0(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0))(vitest@5.0.0) @@ -3200,8 +2862,8 @@ snapshots: '@vitest/coverage-v8@5.0.0(@vitest/browser@5.0.0)(vitest@5.0.0)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/istanbul-lib-coverage': 1.0.1 - '@vitest/istanbul-lib-report': 1.0.1 + '@vitest/istanbul-lib-coverage': link:packages/istanbul-lib-coverage + '@vitest/istanbul-lib-report': link:packages/istanbul-lib-report ast-v8-to-istanbul: 1.0.5 magicast: 0.5.4 obug: 2.1.4 @@ -3211,12 +2873,6 @@ snapshots: optionalDependencies: '@vitest/browser': 5.0.0(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0))(vitest@5.0.0) - '@vitest/istanbul-lib-coverage@1.0.1': {} - - '@vitest/istanbul-lib-report@1.0.1': - dependencies: - '@vitest/istanbul-lib-coverage': 1.0.1 - '@vitest/mocker@5.0.0(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -3326,63 +2982,6 @@ snapshots: ansis@4.3.1: {} - antd@6.6.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8): - dependencies: - '@ant-design/colors': 8.0.1 - '@ant-design/cssinjs': 2.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@ant-design/cssinjs-utils': 2.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@ant-design/fast-color': 3.0.1 - '@ant-design/icons': 6.3.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@ant-design/react-slick': 2.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@babel/runtime': 7.29.7 - '@rc-component/cascader': 1.22.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/checkbox': 2.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/collapse': 1.2.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/color-picker': 3.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/dialog': 1.10.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/drawer': 1.4.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/dropdown': 1.0.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/form': 1.8.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/image': 1.10.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/input': 1.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/input-number': 1.6.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/listy': 1.2.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/mentions': 1.11.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/menu': 1.4.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/motion': 1.3.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/mutate-observer': 2.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/notification': 2.0.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/pagination': 1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/picker': 1.12.0(dayjs@1.11.23)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/progress': 1.0.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/qrcode': 2.0.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/rate': 1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/segmented': 1.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/select': 1.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/slider': 1.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/steps': 1.2.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/switch': 1.0.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/table': 1.11.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/tabs': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/tooltip': 1.5.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/tour': 2.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/tree': 1.4.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/tree-select': 1.16.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/trigger': 3.10.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/upload': 1.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@rc-component/util': 1.12.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - clsx: 2.1.1 - dayjs: 1.11.23 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - scroll-into-view-if-needed: 3.1.0 - throttle-debounce: 5.0.2 - transitivePeerDependencies: - - date-fns - - luxon - - moment - anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -3396,10 +2995,16 @@ snapshots: estree-walker: 3.0.3 js-tokens: 10.0.0 + babel-plugin-transform-hook-names@1.0.2(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + baseline-browser-mapping@2.11.15: {} binary-extensions@2.3.0: {} + boolbase@1.0.0: {} + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -3507,12 +3112,8 @@ snapshots: dependencies: consola: 3.4.2 - clsx@2.1.1: {} - colorette@2.0.20: {} - compute-scroll-into-view@3.1.1: {} - confbox@0.1.8: {} confbox@0.2.4: {} @@ -3523,9 +3124,21 @@ snapshots: convert-source-map@2.0.0: {} - csstype@3.2.3: {} + crelt@1.0.7: {} + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-what@6.2.2: {} - dayjs@1.11.23: {} + debug@4.4.3: + dependencies: + ms: 2.1.3 default-browser-id@5.0.1: {} @@ -3542,9 +3155,23 @@ snapshots: detect-libc@2.1.2: {} - dompurify@3.4.8: - optionalDependencies: - '@types/trusted-types': 2.0.7 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 dotenv@16.6.1: {} @@ -3556,10 +3183,14 @@ snapshots: empathic@2.0.1: {} + entities@4.5.0: {} + es-module-lexer@2.3.2: {} escalade@3.2.0: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -3568,9 +3199,9 @@ snapshots: exsolve@1.1.1: {} - fdir@6.5.0(picomatch@4.0.5): + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: - picomatch: 4.0.5 + picomatch: 4.0.7 fflate@0.8.3: {} @@ -3612,7 +3243,7 @@ snapshots: dependencies: is-glob: 4.0.3 - highlight-words-core@1.2.3: {} + he@1.2.0: {} hookable@6.1.1: {} @@ -3636,8 +3267,6 @@ snapshots: dependencies: is-docker: 3.0.0 - is-mobile@5.0.0: {} - is-number@7.0.0: {} is-wsl@3.1.1: @@ -3650,14 +3279,14 @@ snapshots: js-tokens@10.0.0: {} - jsesc@3.1.0: {} + js-tokens@4.0.0: {} - json2mq@0.2.0: - dependencies: - string-convert: 0.2.1 + jsesc@3.1.0: {} json5@2.2.3: {} + kolorist@1.8.0: {} + lightningcss-android-arm64@1.33.0: optional: true @@ -3711,6 +3340,18 @@ snapshots: lru-cache@11.5.2: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-preact@1.45.0(preact@10.29.8): + dependencies: + preact: 10.29.8 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + magic-string@1.3.1: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -3721,10 +3362,6 @@ snapshots: '@babel/types': 7.29.8 source-map-js: 1.2.1 - marked@14.0.0: {} - - memoize-one@4.0.3: {} - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -3752,23 +3389,29 @@ snapshots: module-replacements@3.2.0: {} - monaco-editor@0.56.0: - dependencies: - dompurify: 3.4.8 - marked: 14.0.0 - mri@1.2.0: {} mrmime@2.0.1: {} + ms@2.1.3: {} + nanoid@3.3.18: {} node-fetch-native@1.6.7: {} + node-html-parser@6.1.13: + dependencies: + css-select: 5.2.2 + he: 1.2.0 + node-releases@2.0.53: {} normalize-path@3.0.0: {} + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + nypm@0.5.4: dependencies: citty: 0.1.6 @@ -3909,21 +3552,6 @@ snapshots: defu: 6.1.7 destr: 2.0.5 - react-dom@19.2.8(react@19.2.8): - dependencies: - react: 19.2.8 - scheduler: 0.27.0 - - react-highlight-words@0.21.0(react@19.2.8): - dependencies: - highlight-words-core: 1.2.3 - memoize-one: 4.0.3 - react: 19.2.8 - - react-is@19.2.8: {} - - react@19.2.8: {} - readdirp@3.6.0: dependencies: picomatch: 2.3.2 @@ -3969,18 +3597,18 @@ snapshots: run-applescript@7.1.0: {} - scheduler@0.27.0: {} - - scroll-into-view-if-needed@3.1.0: - dependencies: - compute-scroll-into-view: 3.1.1 - scule@1.3.0: {} + semver@6.3.1: {} + semver@7.8.5: {} siginfo@2.0.0: {} + simple-code-frame@1.3.0: + dependencies: + kolorist: 1.8.0 + sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 @@ -3989,15 +3617,17 @@ snapshots: source-map-js@1.2.1: {} + source-map@0.7.6: {} + + stack-trace@1.0.0: {} + stackback@0.0.2: {} std-env@3.10.0: {} std-env@4.2.0: {} - string-convert@0.2.1: {} - - stylis@4.4.0: {} + style-mod@4.1.3: {} tar@6.2.1: dependencies: @@ -4008,8 +3638,6 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - throttle-debounce@5.0.2: {} - tinybench@6.1.4: {} tinyexec@0.3.2: {} @@ -4018,8 +3646,8 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 tinypool@2.1.0: {} @@ -4104,10 +3732,20 @@ snapshots: micromatch: 4.0.8 vite: 8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0) + vite-prerender-plugin@0.5.13(vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0)): + dependencies: + kolorist: 1.8.0 + magic-string: 0.30.21 + node-html-parser: 6.1.13 + simple-code-frame: 1.3.0 + source-map: 0.7.6 + stack-trace: 1.0.0 + vite: 8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0) + vite@8.2.2(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 - picomatch: 4.0.5 + picomatch: 4.0.7 postcss: 8.5.26 rolldown: 1.2.5 tinyglobby: 0.2.17 @@ -4141,6 +3779,8 @@ snapshots: transitivePeerDependencies: - msw + w3c-keyname@2.2.8: {} + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -4152,6 +3792,8 @@ snapshots: dependencies: is-wsl: 3.1.1 + yallist@3.1.1: {} + yallist@4.0.0: {} yaml@2.9.0: {} @@ -4194,3 +3836,5 @@ snapshots: '@yuku-parser/binding-linux-x64-musl': 0.8.7 '@yuku-parser/binding-win32-arm64': 0.8.7 '@yuku-parser/binding-win32-x64': 0.8.7 + + zimmerframe@1.1.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5a7c265..b5d512e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,6 +5,9 @@ cleanupUnusedCatalogs: true ignoreWorkspaceRootCheck: true shellEmulator: true trustPolicy: no-downgrade +trustPolicyExclude: + # Transitive dep of @babel/core@7.x; predates npm provenance (false positive) + - semver@6.3.1 allowBuilds: "fsevents": true packages: @@ -29,18 +32,17 @@ catalog: typescript: ^7.0.2 "@e18e/eslint-plugin": ^0.8.0 changelogithub: ^15.0.4 - "@ant-design/icons": ^6.3.2 - antd: ^6.6.1 - monaco-editor: ^0.56.0 - react: ^19.2.8 - react-dom: ^19.2.8 - react-highlight-words: ^0.21.0 - "@types/react": ^19.2.18 - "@types/react-dom": ^19.2.4 - "@types/react-highlight-words": ^0.20.1 - "@vitejs/plugin-react": ^6.1.0 vite: ^8.2.2 vite-plugin-singlefile: ^2.3.3 + picomatch: ^4.0.7 + "@types/picomatch": ^4.0.3 + lucide-preact: ^1.28.0 + "@preact/preset-vite": ^2.10.2 + "@codemirror/lang-javascript": ^6.2.4 + "@codemirror/language": ^6.11.3 + "@codemirror/state": ^6.5.2 + "@codemirror/theme-one-dark": ^6.1.3 + "@codemirror/view": ^6.38.6 minimumReleaseAgeExclude: - "@vitest/browser-playwright@5.0.0" diff --git a/tsconfig.json b/tsconfig.json index 868958a..0f31ac4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,5 @@ "@vitest/istanbul-lib-source-maps": ["./packages/istanbul-lib-source-maps/src/index.ts"] } }, - "include": ["packages/*/src", "packages/*/test"], - "exclude": ["packages/istanbul-report-html-modern"] + "include": ["packages/*/src", "packages/*/test"] } diff --git a/vitest.config.ts b/vitest.config.ts index 9eae8cf..dfd2cd0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ enabled: true, provider: "v8", include: ["packages/**/src/**/*.ts"], - reporter: ["json", [htmlModernReporter, { writeReportDataJson: true }]], + reporter: ["json", ["html-modern", { writeReportDataJson: true }]], }, projects: ["packages/*/vitest.config.*"], }, From 70a4f3417fc231fe795438b62def6aeb3a5da1af Mon Sep 17 00:00:00 2001 From: Travis Zhang Date: Tue, 15 Sep 2026 11:26:13 +0800 Subject: [PATCH 12/15] chore: add package fileTags for html-modern coverage report --- vitest.config.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/vitest.config.ts b/vitest.config.ts index dfd2cd0..8789549 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,7 +9,22 @@ export default defineConfig({ enabled: true, provider: "v8", include: ["packages/**/src/**/*.ts"], - reporter: ["json", ["html-modern", { writeReportDataJson: true }]], + reporter: [ + "json", + [ + "html-modern", + { + writeReportDataJson: true, + fileTags: [ + { glob: "packages/istanbul-lib-coverage/**", tag: "lib-coverage" }, + { glob: "packages/istanbul-lib-instrument/**", tag: "lib-instrument" }, + { glob: "packages/istanbul-lib-report/**", tag: "lib-report" }, + { glob: "packages/istanbul-lib-source-maps/**", tag: "lib-source-maps" }, + { glob: "packages/istanbul-report-html-modern/**", tag: "html-modern" }, + ], + }, + ], + ], }, projects: ["packages/*/vitest.config.*"], }, From 5350965f1e9d5661e24ed3f4beb8fac5e5ee6ebf Mon Sep 17 00:00:00 2001 From: Travis Zhang Date: Tue, 15 Sep 2026 11:30:01 +0800 Subject: [PATCH 13/15] chore(html-modern): update package author to Travis Zhang --- packages/istanbul-report-html-modern/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/istanbul-report-html-modern/package.json b/packages/istanbul-report-html-modern/package.json index dba5854..7ee2568 100644 --- a/packages/istanbul-report-html-modern/package.json +++ b/packages/istanbul-report-html-modern/package.json @@ -15,7 +15,7 @@ "url": "https://github.com/vitest-dev/vitest/issues" }, "license": "MIT", - "author": "Vitest Team", + "author": "Travis Zhang", "repository": { "type": "git", "url": "git+https://github.com/vitest-dev/istanbuljs.git", From ed5c9e105691e6fe63cacaea74812f78cb9522bf Mon Sep 17 00:00:00 2001 From: Travis Zhang Date: Tue, 15 Sep 2026 11:34:40 +0800 Subject: [PATCH 14/15] chore(html-modern): point report footer credit to vitest --- .../src/page/components/ReportFooter.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx b/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx index ae10a50..bf3d373 100644 --- a/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx +++ b/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx @@ -29,11 +29,11 @@ export function ReportFooter({ generatedAt, packageName, packageVersion }: Repor

Code coverage generated by{" "} - @vitest/istanbul-report-html-modern + vitest {hasPackageInfo ? ( <> From 06b81752efb0505617d06fc65002496143eed136 Mon Sep 17 00:00:00 2001 From: Travis Zhang Date: Tue, 15 Sep 2026 11:36:08 +0800 Subject: [PATCH 15/15] fix: fmt --- .../src/page/components/ReportFooter.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx b/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx index bf3d373..9435a81 100644 --- a/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx +++ b/packages/istanbul-report-html-modern/src/page/components/ReportFooter.tsx @@ -28,11 +28,7 @@ export function ReportFooter({ generatedAt, packageName, packageVersion }: Repor

Code coverage generated by{" "} - + vitest {hasPackageInfo ? (