From c3caf4c946233860991a1a7a20559c45485b39da Mon Sep 17 00:00:00 2001 From: Carlson Santana Cruz Date: Sun, 28 Jun 2026 23:08:02 -0300 Subject: [PATCH 01/15] Don't split the main content at blank headings --- src/split_main_content.ts | 14 +++++++++----- test/split_main_content.test.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/split_main_content.ts b/src/split_main_content.ts index cdc9d84..9f592f7 100644 --- a/src/split_main_content.ts +++ b/src/split_main_content.ts @@ -14,17 +14,17 @@ function splitMainContentByHeadings( mainContent: Element, metadata: {title: string}, ): SplittedElement[] { - let headings = Array.from(mainContent.getElementsByTagName('h2')); + const headingsLevel2 = Array.from(mainContent.getElementsByTagName('h2')).filter(hasTitle); - if (headings.length === 0) { + if (headingsLevel2.length === 0) { return [{title: metadata.title, element: mainContent}]; } - if (headings.length > 1) { - return splitMainContent(mainContent, metadata, headings); + if (headingsLevel2.length > 1) { + return splitMainContent(mainContent, metadata, headingsLevel2); } - headings = Array.from(mainContent.querySelectorAll('h2, h3')); + const headings = Array.from(mainContent.querySelectorAll('h2, h3')).filter(hasTitle); return splitMainContent(mainContent, metadata, headings); } @@ -132,4 +132,8 @@ function moveElementsToNewParent( } } +function hasTitle(heading: Element): boolean { + return heading.textContent.trim() !== ''; +} + export default new Step(DESCRIPTION, splitMainContentByHeadings); diff --git a/test/split_main_content.test.ts b/test/split_main_content.test.ts index f0aca4d..46b2521 100644 --- a/test/split_main_content.test.ts +++ b/test/split_main_content.test.ts @@ -82,6 +82,32 @@ test('omits remaining content before first heading when it is shorter than 80 ch assert.ok(!titles.includes('Article'), 'short intro should be dropped'); }); +test('does not split at a heading that is empty', () => { + const main = makeElement( + '

Content one here.

' + + '

Real Chapter

Content two here.

' + ); + const metadata = { title: 'Book' }; + + const result = splitMainContent.run(main, metadata); + + const titles = result.map(r => r.title); + assert.ok(!titles.some(title => title.trim() === ''), 'blank heading should not create a chapter'); +}); + +test('does not split at a heading that has only whitespace, tabs and line breaks', () => { + const main = makeElement( + '

\t\n\r

Content one here.

' + + '

Real Chapter

Content two here.

' + ); + const metadata = { title: 'Book' }; + + const result = splitMainContent.run(main, metadata); + + const titles = result.map(r => r.title); + assert.ok(!titles.some(title => title.trim() === ''), 'whitespace-only heading should not create a chapter'); +}); + function makeElement(bodyHTML: string): Element { const doc = document.implementation.createHTMLDocument(''); doc.body.innerHTML = bodyHTML; From 64aea6f0ffc5c1ce99bfdf7a753e8056114a3c59 Mon Sep 17 00:00:00 2001 From: Carlson Santana Cruz Date: Tue, 30 Jun 2026 00:09:00 -0300 Subject: [PATCH 02/15] Create a script to verify if the files are being used --- package-lock.json | 1776 +++++++++++++++++++++++++++++++++++++++++---- package.json | 4 +- 2 files changed, 1635 insertions(+), 145 deletions(-) diff --git a/package-lock.json b/package-lock.json index 532989f..227eb91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "fflate": "^0.8.2", "globals": "^17.4.0", "jsdom": "^29.0.2", + "knip": "^6.23.0", "tsx": "^4.21.0", "typescript": "^6.0.3", "typescript-eslint": "^8.58.0" @@ -228,6 +229,40 @@ "node": ">=20.19.0" } }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", @@ -759,113 +794,837 @@ "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.4.tgz", + "integrity": "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.0.tgz", + "integrity": "sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.137.0.tgz", + "integrity": "sha512-KDs+0VPdEmasOkpuJHW9V5WCF+cvYdMQv2Jd+aJXt+cxIx12NToRQRbXaRwUEDsZw+/jMk81Ve8ZFbjUkJTOwA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.137.0.tgz", + "integrity": "sha512-WhALNzfy3x/RfC6bsqX+csavuUY0yHHE7XfgPE5M542uhoBZUUoGTPG+nkMbGoG4+gcfss5s7urMyn5QBHu0sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.137.0.tgz", + "integrity": "sha512-bFPr5hgmNMOMoyPTGtdsK4Ug21RovIPojRMgDDhSp1LtCnc/DkLwGONKjgRjszg677RlGnkYSviQ8hHaUPOVYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.137.0.tgz", + "integrity": "sha512-CL5dMm1asqXIDZHg14FLxj3Mc36w8PI7xCWh1uA4is6z8g2XrIILoTcQYOxDbwzuk34RDPX5IAGUxZr6LA9KAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.137.0.tgz", + "integrity": "sha512-79h8rYGnSlKPGWo7mHr2ixO6ea7aW8B0CT965SZ8SLbNnCOH5aOYBTeVXUY6eMvEaiLyWr8Skuiugr5pDYgLGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.137.0.tgz", + "integrity": "sha512-ASgmlSimhGyr0lksgVIo6hibz1obnDq4qJbiMX/AzltfgPnanRrzG1Q+23g8ljOHOjv6dsznkUuCYL3gg0sY1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.137.0.tgz", + "integrity": "sha512-AU2J9aa22Sx32wRGnDjybOU9TQXXQUud5sdUi+ZB0XxwM8aToWLweV+yA0wlQm0yIUVqljquqoHCYEq9II8gJQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.137.0.tgz", + "integrity": "sha512-GdEtiG89yMr7XkUGxifgodXEEm2f+xW2f9CpDjlgAnBOwhTmrpQMvhOGobLVKUyzf/qHBXW16smk5zbF3nZU6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.137.0.tgz", + "integrity": "sha512-EGJ+Bs8iXx8KBH8DQ5BLoEm5lnHaYjlh4/8j8vFhrr/6z4tqONy5BZDzLpKmmNWlN6Hlc5r8YOuBVHqZ9vRFEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.137.0.tgz", + "integrity": "sha512-vzFUQENy/fnbSe5DZWovq6tIBc1uhuMztanSW6rz1e9WdQE4gHwYuD7ZII6JnrJifd1R3RSoqiZbgRFlVL2tYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.137.0.tgz", + "integrity": "sha512-SfVI14HBQs9gtLcUD5hTt5hsNbdrqSUNg9S8muN+LhVQ5nf1WwH3hAoK6B9NKgdYgWAQSXFXGiiBedQ4r/BKuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.137.0.tgz", + "integrity": "sha512-e7Ppy4FCIFNQxT/ikSeIWFoQ0l+N9vgtRBtLcyZXeolTzApyVoPqEXsYPrcdM/9i0Bwk8knvYd37vaEMxHyi6g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.137.0.tgz", + "integrity": "sha512-Bho5qFwdhqsIFR7gipYEUlqvi3SRrY8sugxXig380MIaakBB1PyU9+7dBiBVScfImTNWhijUxdBwqrprGdq5WA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.137.0.tgz", + "integrity": "sha512-36mGWtg7PyFzjJwGDkH6/F4o2nIDEoKXLPr/X/lwqklkomQwJJt1I5GJVmGhovUEmgPK5WAeAZMqlFCehwiy9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.137.0.tgz", + "integrity": "sha512-/Jqx6+N7A44n2BdvUr7pXhVr2vFjs6WGH3unZRczwrfiH0H1zY0QwKQMG/dtRiTlKGDKGukznPT8lx84/oEsZg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.137.0.tgz", + "integrity": "sha512-9Uj0qHNNl+OgT1UTGwF7ixIXU6T1u2SbMidmgPy/h1h/fl2gRS6YpAxxY1gwHofcWjoTwkoMFd8xs5Vuj6GOFA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.137.0.tgz", + "integrity": "sha512-gW2vfkytNGgMVADiuzdvOfw0mWG9za20F/1fCJsif5aBMAvWJTSbpIXbIe0XkOe0VENk+PadpQ7cZgUy2sUJcA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.5" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.137.0.tgz", + "integrity": "sha512-x+pFANF0yL5uK/6T7lu6SlR5qid6sp//eZXKLq5iNsIE+EQg6EaS8/wsW7E91nXXjpnPhSoMOHXShSVhGRdn8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.137.0.tgz", + "integrity": "sha512-sQUqym80PFi6McRsIqfJrSu2JrSClEZIXXD+/FjAFoULEKzOPsldIdFBG96xdX8aVMzCNQ9792FPx3MfkEIrFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.137.0.tgz", + "integrity": "sha512-2AsevxlvNN4WKxpEn3RtqD5zbqMaXF+T7JXblsP4gVuY+vC9dXS4ED/PwfRCliFqoeisYS3Iro4DHzxr0TEvVA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.21.3.tgz", + "integrity": "sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.21.3.tgz", + "integrity": "sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.21.3.tgz", + "integrity": "sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.21.3.tgz", + "integrity": "sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.21.3.tgz", + "integrity": "sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.21.3.tgz", + "integrity": "sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.21.3.tgz", + "integrity": "sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.21.3.tgz", + "integrity": "sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.21.3.tgz", + "integrity": "sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.21.3.tgz", + "integrity": "sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.21.3.tgz", + "integrity": "sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.21.3.tgz", + "integrity": "sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.21.3.tgz", + "integrity": "sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.21.3.tgz", + "integrity": "sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.21.3.tgz", + "integrity": "sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@eslint/object-schema": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.4.tgz", - "integrity": "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==", + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.21.3.tgz", + "integrity": "sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/@eslint/plugin-kit": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.0.tgz", - "integrity": "sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg==", + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.21.3.tgz", + "integrity": "sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA==", + "cpu": [ + "wasm32" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, "dependencies": { - "@eslint/core": "^1.2.0", - "levn": "^0.4.1" + "@emnapi/core": "1.11.0", + "@emnapi/runtime": "1.11.0", + "@napi-rs/wasm-runtime": "^1.1.5" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=14.0.0" } }, - "node_modules/@exodus/bytes": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", - "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", + "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==", "dev": true, "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" - }, - "peerDependenciesMeta": { - "@noble/hashes": { - "optional": true - } + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", + "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.21.3.tgz", + "integrity": "sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.21.3.tgz", + "integrity": "sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@types/esrecurse": { @@ -1626,6 +2385,34 @@ "dev": true, "license": "MIT" }, + "node_modules/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -1666,6 +2453,22 @@ "dev": true, "license": "ISC" }, + "node_modules/formatly": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz", + "integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fd-package-json": "^2.0.0" + }, + "bin": { + "formatly": "bin/index.mjs" + }, + "engines": { + "node": ">=18.3.0" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1798,6 +2601,16 @@ "fflate": "^0.8.2" } }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/jsdom": { "version": "29.0.2", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", @@ -1882,6 +2695,45 @@ "json-buffer": "3.0.1" } }, + "node_modules/knip": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/knip/-/knip-6.23.0.tgz", + "integrity": "sha512-2DvAOX2pZWiG4SLvRRxOAU0aWGEn1ZoVblI541xIoXFdHqq2THMZXy66/qcY5WGuW3TXhb9T1x1zd/Hd1u+yqg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + } + ], + "license": "ISC", + "dependencies": { + "fdir": "^6.5.0", + "formatly": "^0.3.0", + "get-tsconfig": "4.14.0", + "jiti": "^2.7.0", + "oxc-parser": "^0.137.0", + "oxc-resolver": "11.21.3", + "picomatch": "^4.0.4", + "smol-toml": "^1.6.1", + "strip-json-comments": "5.0.3", + "tinyglobby": "^0.2.17", + "unbash": "^4.0.1", + "yaml": "^2.9.0", + "zod": "^4.1.11" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -1961,6 +2813,75 @@ "node": ">= 0.8.0" } }, + "node_modules/oxc-parser": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.137.0.tgz", + "integrity": "sha512-yFImD+WLElJpLKy8llG1qe4DCmMsL18peRp8XP1JKfig/gISbJkglnpDtX2aTmAn10kZF7164HbN2H8QPsXxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.137.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.137.0", + "@oxc-parser/binding-android-arm64": "0.137.0", + "@oxc-parser/binding-darwin-arm64": "0.137.0", + "@oxc-parser/binding-darwin-x64": "0.137.0", + "@oxc-parser/binding-freebsd-x64": "0.137.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.137.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.137.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.137.0", + "@oxc-parser/binding-linux-arm64-musl": "0.137.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.137.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.137.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.137.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.137.0", + "@oxc-parser/binding-linux-x64-gnu": "0.137.0", + "@oxc-parser/binding-linux-x64-musl": "0.137.0", + "@oxc-parser/binding-openharmony-arm64": "0.137.0", + "@oxc-parser/binding-wasm32-wasi": "0.137.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.137.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.137.0", + "@oxc-parser/binding-win32-x64-msvc": "0.137.0" + } + }, + "node_modules/oxc-resolver": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.21.3.tgz", + "integrity": "sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.21.3", + "@oxc-resolver/binding-android-arm64": "11.21.3", + "@oxc-resolver/binding-darwin-arm64": "11.21.3", + "@oxc-resolver/binding-darwin-x64": "11.21.3", + "@oxc-resolver/binding-freebsd-x64": "11.21.3", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.3", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.21.3", + "@oxc-resolver/binding-linux-arm64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-arm64-musl": "11.21.3", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-riscv64-musl": "11.21.3", + "@oxc-resolver/binding-linux-s390x-gnu": "11.21.3", + "@oxc-resolver/binding-linux-x64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-x64-musl": "11.21.3", + "@oxc-resolver/binding-openharmony-arm64": "11.21.3", + "@oxc-resolver/binding-wasm32-wasi": "11.21.3", + "@oxc-resolver/binding-win32-arm64-msvc": "11.21.3", + "@oxc-resolver/binding-win32-x64-msvc": "11.21.3" + } + }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -1992,6 +2913,19 @@ "node": ">=8" } }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2079,6 +3013,19 @@ "node": ">=8" } }, + "node_modules/smol-toml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz", + "integrity": "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2089,59 +3036,41 @@ "node": ">=0.10.0" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", "dev": true, "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, "engines": { - "node": ">=12.0.0" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, "engines": { - "node": ">=12" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/tldts": { @@ -2203,6 +3132,14 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/tsx": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", @@ -2274,6 +3211,16 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/unbash": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unbash/-/unbash-4.0.2.tgz", + "integrity": "sha512-8gwNZ29+0/3zmXw7ToIHZtg6wK37xnniRUdBt7B27xZxaxfgR5tGMaGHT0t0dLtBV9fXE7zurh0s6Z1DHVjfWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/undici": { "version": "7.25.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", @@ -2313,6 +3260,16 @@ "node": ">=18" } }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/webidl-conversions": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", @@ -2390,6 +3347,22 @@ "dev": true, "license": "MIT" }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -2402,6 +3375,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } }, "dependencies": { @@ -2495,6 +3478,37 @@ "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true }, + "@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "optional": true, + "requires": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, "@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", @@ -2787,6 +3801,338 @@ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true }, + "@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "optional": true, + "requires": { + "@tybys/wasm-util": "^0.10.3" + } + }, + "@oxc-parser/binding-android-arm-eabi": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.137.0.tgz", + "integrity": "sha512-KDs+0VPdEmasOkpuJHW9V5WCF+cvYdMQv2Jd+aJXt+cxIx12NToRQRbXaRwUEDsZw+/jMk81Ve8ZFbjUkJTOwA==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-android-arm64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.137.0.tgz", + "integrity": "sha512-WhALNzfy3x/RfC6bsqX+csavuUY0yHHE7XfgPE5M542uhoBZUUoGTPG+nkMbGoG4+gcfss5s7urMyn5QBHu0sw==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-darwin-arm64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.137.0.tgz", + "integrity": "sha512-bFPr5hgmNMOMoyPTGtdsK4Ug21RovIPojRMgDDhSp1LtCnc/DkLwGONKjgRjszg677RlGnkYSviQ8hHaUPOVYA==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-darwin-x64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.137.0.tgz", + "integrity": "sha512-CL5dMm1asqXIDZHg14FLxj3Mc36w8PI7xCWh1uA4is6z8g2XrIILoTcQYOxDbwzuk34RDPX5IAGUxZr6LA9KAg==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-freebsd-x64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.137.0.tgz", + "integrity": "sha512-79h8rYGnSlKPGWo7mHr2ixO6ea7aW8B0CT965SZ8SLbNnCOH5aOYBTeVXUY6eMvEaiLyWr8Skuiugr5pDYgLGw==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.137.0.tgz", + "integrity": "sha512-ASgmlSimhGyr0lksgVIo6hibz1obnDq4qJbiMX/AzltfgPnanRrzG1Q+23g8ljOHOjv6dsznkUuCYL3gg0sY1Q==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.137.0.tgz", + "integrity": "sha512-AU2J9aa22Sx32wRGnDjybOU9TQXXQUud5sdUi+ZB0XxwM8aToWLweV+yA0wlQm0yIUVqljquqoHCYEq9II8gJQ==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.137.0.tgz", + "integrity": "sha512-GdEtiG89yMr7XkUGxifgodXEEm2f+xW2f9CpDjlgAnBOwhTmrpQMvhOGobLVKUyzf/qHBXW16smk5zbF3nZU6w==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-arm64-musl": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.137.0.tgz", + "integrity": "sha512-EGJ+Bs8iXx8KBH8DQ5BLoEm5lnHaYjlh4/8j8vFhrr/6z4tqONy5BZDzLpKmmNWlN6Hlc5r8YOuBVHqZ9vRFEQ==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.137.0.tgz", + "integrity": "sha512-vzFUQENy/fnbSe5DZWovq6tIBc1uhuMztanSW6rz1e9WdQE4gHwYuD7ZII6JnrJifd1R3RSoqiZbgRFlVL2tYQ==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.137.0.tgz", + "integrity": "sha512-SfVI14HBQs9gtLcUD5hTt5hsNbdrqSUNg9S8muN+LhVQ5nf1WwH3hAoK6B9NKgdYgWAQSXFXGiiBedQ4r/BKuw==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.137.0.tgz", + "integrity": "sha512-e7Ppy4FCIFNQxT/ikSeIWFoQ0l+N9vgtRBtLcyZXeolTzApyVoPqEXsYPrcdM/9i0Bwk8knvYd37vaEMxHyi6g==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.137.0.tgz", + "integrity": "sha512-Bho5qFwdhqsIFR7gipYEUlqvi3SRrY8sugxXig380MIaakBB1PyU9+7dBiBVScfImTNWhijUxdBwqrprGdq5WA==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-x64-gnu": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.137.0.tgz", + "integrity": "sha512-36mGWtg7PyFzjJwGDkH6/F4o2nIDEoKXLPr/X/lwqklkomQwJJt1I5GJVmGhovUEmgPK5WAeAZMqlFCehwiy9Q==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-x64-musl": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.137.0.tgz", + "integrity": "sha512-/Jqx6+N7A44n2BdvUr7pXhVr2vFjs6WGH3unZRczwrfiH0H1zY0QwKQMG/dtRiTlKGDKGukznPT8lx84/oEsZg==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-openharmony-arm64": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.137.0.tgz", + "integrity": "sha512-9Uj0qHNNl+OgT1UTGwF7ixIXU6T1u2SbMidmgPy/h1h/fl2gRS6YpAxxY1gwHofcWjoTwkoMFd8xs5Vuj6GOFA==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-wasm32-wasi": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.137.0.tgz", + "integrity": "sha512-gW2vfkytNGgMVADiuzdvOfw0mWG9za20F/1fCJsif5aBMAvWJTSbpIXbIe0XkOe0VENk+PadpQ7cZgUy2sUJcA==", + "dev": true, + "optional": true, + "requires": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.5" + } + }, + "@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.137.0.tgz", + "integrity": "sha512-x+pFANF0yL5uK/6T7lu6SlR5qid6sp//eZXKLq5iNsIE+EQg6EaS8/wsW7E91nXXjpnPhSoMOHXShSVhGRdn8w==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.137.0.tgz", + "integrity": "sha512-sQUqym80PFi6McRsIqfJrSu2JrSClEZIXXD+/FjAFoULEKzOPsldIdFBG96xdX8aVMzCNQ9792FPx3MfkEIrFA==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-win32-x64-msvc": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.137.0.tgz", + "integrity": "sha512-2AsevxlvNN4WKxpEn3RtqD5zbqMaXF+T7JXblsP4gVuY+vC9dXS4ED/PwfRCliFqoeisYS3Iro4DHzxr0TEvVA==", + "dev": true, + "optional": true + }, + "@oxc-project/types": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "dev": true + }, + "@oxc-resolver/binding-android-arm-eabi": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.21.3.tgz", + "integrity": "sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-android-arm64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.21.3.tgz", + "integrity": "sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-darwin-arm64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.21.3.tgz", + "integrity": "sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-darwin-x64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.21.3.tgz", + "integrity": "sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-freebsd-x64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.21.3.tgz", + "integrity": "sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.21.3.tgz", + "integrity": "sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.21.3.tgz", + "integrity": "sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.21.3.tgz", + "integrity": "sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.21.3.tgz", + "integrity": "sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.21.3.tgz", + "integrity": "sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.21.3.tgz", + "integrity": "sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.21.3.tgz", + "integrity": "sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.21.3.tgz", + "integrity": "sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.21.3.tgz", + "integrity": "sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-linux-x64-musl": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.21.3.tgz", + "integrity": "sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-openharmony-arm64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.21.3.tgz", + "integrity": "sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-wasm32-wasi": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.21.3.tgz", + "integrity": "sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA==", + "dev": true, + "optional": true, + "requires": { + "@emnapi/core": "1.11.0", + "@emnapi/runtime": "1.11.0", + "@napi-rs/wasm-runtime": "^1.1.5" + }, + "dependencies": { + "@emnapi/core": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", + "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==", + "dev": true, + "optional": true, + "requires": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "@emnapi/runtime": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", + "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + } + } + }, + "@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.21.3.tgz", + "integrity": "sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg==", + "dev": true, + "optional": true + }, + "@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.21.3.tgz", + "integrity": "sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q==", + "dev": true, + "optional": true + }, + "@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, "@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -3280,6 +4626,22 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "dev": true, + "requires": { + "walk-up-path": "^4.0.0" + } + }, + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, "fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -3310,6 +4672,15 @@ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, + "formatly": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz", + "integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==", + "dev": true, + "requires": { + "fd-package-json": "^2.0.0" + } + }, "fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3395,6 +4766,12 @@ "from": "jepub@github:webpagetoepub/jEpub#48beea3b617201745ff2a2467d47b9d978dff028", "requires": {} }, + "jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true + }, "jsdom": { "version": "29.0.2", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", @@ -3462,6 +4839,27 @@ "json-buffer": "3.0.1" } }, + "knip": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/knip/-/knip-6.23.0.tgz", + "integrity": "sha512-2DvAOX2pZWiG4SLvRRxOAU0aWGEn1ZoVblI541xIoXFdHqq2THMZXy66/qcY5WGuW3TXhb9T1x1zd/Hd1u+yqg==", + "dev": true, + "requires": { + "fdir": "^6.5.0", + "formatly": "^0.3.0", + "get-tsconfig": "4.14.0", + "jiti": "^2.7.0", + "oxc-parser": "^0.137.0", + "oxc-resolver": "11.21.3", + "picomatch": "^4.0.4", + "smol-toml": "^1.6.1", + "strip-json-comments": "5.0.3", + "tinyglobby": "^0.2.17", + "unbash": "^4.0.1", + "yaml": "^2.9.0", + "zod": "^4.1.11" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3519,6 +4917,62 @@ "word-wrap": "^1.2.5" } }, + "oxc-parser": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.137.0.tgz", + "integrity": "sha512-yFImD+WLElJpLKy8llG1qe4DCmMsL18peRp8XP1JKfig/gISbJkglnpDtX2aTmAn10kZF7164HbN2H8QPsXxGg==", + "dev": true, + "requires": { + "@oxc-parser/binding-android-arm-eabi": "0.137.0", + "@oxc-parser/binding-android-arm64": "0.137.0", + "@oxc-parser/binding-darwin-arm64": "0.137.0", + "@oxc-parser/binding-darwin-x64": "0.137.0", + "@oxc-parser/binding-freebsd-x64": "0.137.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.137.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.137.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.137.0", + "@oxc-parser/binding-linux-arm64-musl": "0.137.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.137.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.137.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.137.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.137.0", + "@oxc-parser/binding-linux-x64-gnu": "0.137.0", + "@oxc-parser/binding-linux-x64-musl": "0.137.0", + "@oxc-parser/binding-openharmony-arm64": "0.137.0", + "@oxc-parser/binding-wasm32-wasi": "0.137.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.137.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.137.0", + "@oxc-parser/binding-win32-x64-msvc": "0.137.0", + "@oxc-project/types": "^0.137.0" + } + }, + "oxc-resolver": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.21.3.tgz", + "integrity": "sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA==", + "dev": true, + "requires": { + "@oxc-resolver/binding-android-arm-eabi": "11.21.3", + "@oxc-resolver/binding-android-arm64": "11.21.3", + "@oxc-resolver/binding-darwin-arm64": "11.21.3", + "@oxc-resolver/binding-darwin-x64": "11.21.3", + "@oxc-resolver/binding-freebsd-x64": "11.21.3", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.3", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.21.3", + "@oxc-resolver/binding-linux-arm64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-arm64-musl": "11.21.3", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-riscv64-musl": "11.21.3", + "@oxc-resolver/binding-linux-s390x-gnu": "11.21.3", + "@oxc-resolver/binding-linux-x64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-x64-musl": "11.21.3", + "@oxc-resolver/binding-openharmony-arm64": "11.21.3", + "@oxc-resolver/binding-wasm32-wasi": "11.21.3", + "@oxc-resolver/binding-win32-arm64-msvc": "11.21.3", + "@oxc-resolver/binding-win32-x64-msvc": "11.21.3" + } + }, "parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -3540,6 +4994,12 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3594,12 +5054,24 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "smol-toml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz", + "integrity": "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==", + "dev": true + }, "source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, + "strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "dev": true + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -3607,28 +5079,13 @@ "dev": true }, "tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "requires": { "fdir": "^6.5.0", "picomatch": "^4.0.4" - }, - "dependencies": { - "fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "requires": {} - }, - "picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true - } } }, "tldts": { @@ -3671,6 +5128,13 @@ "dev": true, "requires": {} }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "optional": true + }, "tsx": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", @@ -3709,6 +5173,12 @@ "@typescript-eslint/utils": "8.58.0" } }, + "unbash": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unbash/-/unbash-4.0.2.tgz", + "integrity": "sha512-8gwNZ29+0/3zmXw7ToIHZtg6wK37xnniRUdBt7B27xZxaxfgR5tGMaGHT0t0dLtBV9fXE7zurh0s6Z1DHVjfWg==", + "dev": true + }, "undici": { "version": "7.25.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", @@ -3739,6 +5209,12 @@ "xml-name-validator": "^5.0.0" } }, + "walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true + }, "webidl-conversions": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", @@ -3789,11 +5265,23 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true } } } diff --git a/package.json b/package.json index ffe30ee..2cebda9 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "scripts": { "test": "tsx --require ./test/setup.ts --test test/*.test.ts test/**/*.test.ts", "lint": "eslint src *.mjs", - "typecheck": "tsc --noEmit --skipLibCheck" + "typecheck": "tsc --noEmit --skipLibCheck", + "check:unused-files": "knip --production --include files" }, "repository": { "type": "git", @@ -27,6 +28,7 @@ "fflate": "^0.8.2", "globals": "^17.4.0", "jsdom": "^29.0.2", + "knip": "^6.23.0", "tsx": "^4.21.0", "typescript": "^6.0.3", "typescript-eslint": "^8.58.0" From bfcf101b2cd2be0e6ae53987c69872c68c405d4f Mon Sep 17 00:00:00 2001 From: Carlson Santana Cruz Date: Tue, 30 Jun 2026 00:19:02 -0300 Subject: [PATCH 03/15] Use step --- src/replace_elements/index.ts | 10 ++++++++++ test/convert_document_to_epub.test.ts | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/replace_elements/index.ts b/src/replace_elements/index.ts index 8c072d4..ea5d839 100644 --- a/src/replace_elements/index.ts +++ b/src/replace_elements/index.ts @@ -2,6 +2,7 @@ import { Step, SubProcessStep, Process } from '../step'; import reduceHeadingLevelPage from './reduce_heading_level'; import replaceUnknownElements from './replace_unknown_elements'; import replaceSimpleElementsTag from './replace_simple_elements_tag'; +import replaceElementsByOtherWithCSS from './replace_elements_by_others_with_css'; const DESCRIPTION = 'Replace HTML elements'; @@ -14,6 +15,15 @@ function buildReplaceElementsProcess(htmlDoc: HTMLDocument): Process { {step: reduceHeadingLevelPage, dependencies: [htmlDocStep]}, {step: replaceSimpleElementsTag, dependencies: [htmlDocStep]}, {step: replaceUnknownElements, dependencies: [htmlDocStep]}, + { + step: replaceElementsByOtherWithCSS, + dependencies: [ + htmlDocStep, + reduceHeadingLevelPage, + replaceSimpleElementsTag, + replaceUnknownElements, + ] + }, ]); } diff --git a/test/convert_document_to_epub.test.ts b/test/convert_document_to_epub.test.ts index 8ffa633..3944f18 100644 --- a/test/convert_document_to_epub.test.ts +++ b/test/convert_document_to_epub.test.ts @@ -3,6 +3,7 @@ import * as assert from 'node:assert/strict'; import { unzipSync, strFromU8 } from 'fflate'; import convertDocumentToEPub from '../src/index'; +const STEPS_LENGTH = 31; const HTML = ` @@ -56,8 +57,8 @@ test('reports correct total step count and sequential progress through all sub-s new MockLogger(), ); - const expectedSteps = Array.from({ length: 30 }, (_, i) => i + 1); - assert.strictEqual(reportedLength, 30); + const expectedSteps = Array.from({ length: STEPS_LENGTH }, (_, i) => i + 1); + assert.strictEqual(reportedLength, STEPS_LENGTH); assert.deepStrictEqual(reportedSteps, expectedSteps); }); From b8dc6538b3c53fb44f8f9795808414289a645a23 Mon Sep 17 00:00:00 2001 From: Carlson Santana Cruz Date: Thu, 9 Jul 2026 22:21:02 -0300 Subject: [PATCH 04/15] Create table with visible border when convert to a ePub --- .../replace_elements_by_others_with_css.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/replace_elements/replace_elements_by_others_with_css.ts b/src/replace_elements/replace_elements_by_others_with_css.ts index 6845676..1e528b6 100644 --- a/src/replace_elements/replace_elements_by_others_with_css.ts +++ b/src/replace_elements/replace_elements_by_others_with_css.ts @@ -22,7 +22,19 @@ const SIMPLE_TAGS: MyMap = { 'center': { tag: 'div', properties: {'text-align': 'center'}, - } + }, + 'table': { + tag: 'table', + properties: {'border-collapse': 'collapse'}, + }, + 'th': { + tag: 'th', + properties: {'border': '1px solid black'}, + }, + 'td': { + tag: 'td', + properties: {'border': '1px solid black'}, + }, }; const DESCRIPTION = 'Replace elements by others with CSS style'; From eb99bc53b2b1995b43b0eef2b5b73ac0ccfd1469 Mon Sep 17 00:00:00 2001 From: Carlson Santana Cruz Date: Thu, 9 Jul 2026 22:40:15 -0300 Subject: [PATCH 05/15] Create a script to format the code using prettier --- package-lock.json | 23 +++++++++++++++++++++++ package.json | 3 +++ 2 files changed, 26 insertions(+) diff --git a/package-lock.json b/package-lock.json index 227eb91..9803150 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "globals": "^17.4.0", "jsdom": "^29.0.2", "knip": "^6.23.0", + "prettier": "3.9.5", "tsx": "^4.21.0", "typescript": "^6.0.3", "typescript-eslint": "^8.58.0" @@ -2936,6 +2937,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5006,6 +5023,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", + "dev": true + }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 2cebda9..a3e9f1e 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "scripts": { "test": "tsx --require ./test/setup.ts --test test/*.test.ts test/**/*.test.ts", "lint": "eslint src *.mjs", + "format": "prettier --write \"{src,test}/**/*.ts\" \"*.mjs\"", + "format:check": "prettier --check \"{src,test}/**/*.ts\" \"*.mjs\"", "typecheck": "tsc --noEmit --skipLibCheck", "check:unused-files": "knip --production --include files" }, @@ -29,6 +31,7 @@ "globals": "^17.4.0", "jsdom": "^29.0.2", "knip": "^6.23.0", + "prettier": "3.9.5", "tsx": "^4.21.0", "typescript": "^6.0.3", "typescript-eslint": "^8.58.0" From a1c285ce65c95ec30d794bd41383f375906ee973 Mon Sep 17 00:00:00 2001 From: Carlson Santana Cruz Date: Thu, 9 Jul 2026 22:42:14 -0300 Subject: [PATCH 06/15] Format the code files using prettier --- eslint.config.mjs | 30 ++-- src/clean_document/index.ts | 41 +++--- src/clean_document/remove_attributes.ts | 124 ++++++++++++---- src/clean_document/remove_comments.ts | 7 +- src/clean_document/remove_data_attributes.ts | 12 +- src/clean_document/remove_elements.ts | 54 +++++-- src/clean_document/remove_empty_elements.ts | 56 ++++++-- src/clean_document/remove_empty_svg.ts | 37 ++--- src/clean_document/remove_hidden_elements.ts | 5 +- src/clean_document/remove_whitespaces.ts | 26 ++-- src/convert_text_dom.ts | 8 +- src/create_epub.ts | 49 ++++--- src/fix_links/index.ts | 38 ++--- src/fix_links/remove_broken_anchor_links.ts | 24 ++-- src/fix_links/replace_url_links.ts | 18 +-- src/fix_links/set_external_links_blank.ts | 14 +- src/get_main_content.ts | 9 +- src/get_metadata.ts | 75 +++++----- src/index.ts | 75 ++++++---- src/load_images.ts | 114 ++++++++------- src/replace_element.ts | 5 +- src/replace_elements/index.ts | 25 ++-- src/replace_elements/reduce_heading_level.ts | 30 ++-- .../replace_elements_by_others_with_css.ts | 66 +++++---- .../replace_simple_elements_tag.ts | 38 +++-- .../replace_unknown_elements.ts | 104 +++++++++++--- src/split_main_content.ts | 51 ++++--- src/step.ts | 30 ++-- test/clean_document/remove_attributes.test.ts | 64 +++++---- test/clean_document/remove_comments.test.ts | 41 +++--- .../remove_data_attributes.test.ts | 48 ++++--- test/clean_document/remove_elements.test.ts | 88 ++++++------ .../remove_empty_elements.test.ts | 62 ++++---- test/clean_document/remove_empty_svg.test.ts | 40 +++--- .../remove_hidden_elements.test.ts | 50 +++---- .../clean_document/remove_whitespaces.test.ts | 54 +++---- test/convert_document_to_epub.test.ts | 71 ++++++---- test/convert_text_dom.test.ts | 34 +++-- .../remove_broken_anchor_links.test.ts | 61 ++++---- test/fix_links/replace_url_links.test.ts | 54 +++---- .../set_external_links_blank.test.ts | 28 ++-- test/get_main_content.test.ts | 50 ++++--- test/get_metadata.test.ts | 132 ++++++++++-------- test/load_images.test.ts | 67 +++++---- .../reduce_heading_level.test.ts | 64 +++++---- ...eplace_elements_by_others_with_css.test.ts | 74 +++++----- .../replace_simple_elements_tag.test.ts | 86 ++++++------ .../replace_unknown_elements.test.ts | 82 ++++++----- test/setup.ts | 29 ++-- test/split_main_content.test.ts | 120 +++++++++------- 50 files changed, 1481 insertions(+), 1083 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 5974d5e..2e9fff7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,31 +1,31 @@ -import { defineConfig } from 'eslint/config'; -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; -import globals from 'globals'; +import { defineConfig } from "eslint/config"; +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import globals from "globals"; export default defineConfig( eslint.configs.recommended, tseslint.configs.recommended, tseslint.configs.stylistic, { - 'rules': { - '@typescript-eslint/no-unused-vars': [ - 'error', + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", { - 'args': 'all', - 'argsIgnorePattern': '^_', - 'caughtErrors': 'all', - 'caughtErrorsIgnorePattern': '^_', - 'destructuredArrayIgnorePattern': '^_', - 'varsIgnorePattern': '^_', - 'ignoreRestSiblings': true, + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + ignoreRestSiblings: true, }, ], }, languageOptions: { globals: { ...globals.node, - } + }, }, }, ); diff --git a/src/clean_document/index.ts b/src/clean_document/index.ts index 23056ef..263177b 100644 --- a/src/clean_document/index.ts +++ b/src/clean_document/index.ts @@ -1,29 +1,28 @@ -import removeElementsFromDocument from './remove_elements'; -import removeHiddenElements from './remove_hidden_elements'; -import removeAllComments from './remove_comments'; -import removeEmptyElements from './remove_empty_elements'; -import removeDataAttributes from './remove_data_attributes'; -import removeAttributes from './remove_attributes'; -import removeExtraWhitespacesFromDocument from './remove_whitespaces'; -import removeEmptySVGs from './remove_empty_svg'; -import { Step, SubProcessStep, Process } from '../step'; - -const DESCRIPTION = 'Cleaning HTML document'; +import removeElementsFromDocument from "./remove_elements"; +import removeHiddenElements from "./remove_hidden_elements"; +import removeAllComments from "./remove_comments"; +import removeEmptyElements from "./remove_empty_elements"; +import removeDataAttributes from "./remove_data_attributes"; +import removeAttributes from "./remove_attributes"; +import removeExtraWhitespacesFromDocument from "./remove_whitespaces"; +import removeEmptySVGs from "./remove_empty_svg"; +import { Step, SubProcessStep, Process } from "../step"; +const DESCRIPTION = "Cleaning HTML document"; function buildCleanDocumentProcess(htmlDoc: HTMLDocument): Process { - const firstStep = new Step('HTML document step', () => htmlDoc); + const firstStep = new Step("HTML document step", () => htmlDoc); return new Process([ - {step: firstStep}, - {step: removeElementsFromDocument, dependencies: [firstStep]}, - {step: removeHiddenElements, dependencies: [firstStep]}, - {step: removeAllComments, dependencies: [firstStep]}, - {step: removeEmptyElements, dependencies: [firstStep]}, - {step: removeDataAttributes, dependencies: [firstStep]}, - {step: removeAttributes, dependencies: [firstStep]}, - {step: removeExtraWhitespacesFromDocument, dependencies: [firstStep]}, - {step: removeEmptySVGs, dependencies: [firstStep]}, + { step: firstStep }, + { step: removeElementsFromDocument, dependencies: [firstStep] }, + { step: removeHiddenElements, dependencies: [firstStep] }, + { step: removeAllComments, dependencies: [firstStep] }, + { step: removeEmptyElements, dependencies: [firstStep] }, + { step: removeDataAttributes, dependencies: [firstStep] }, + { step: removeAttributes, dependencies: [firstStep] }, + { step: removeExtraWhitespacesFromDocument, dependencies: [firstStep] }, + { step: removeEmptySVGs, dependencies: [firstStep] }, ]); } diff --git a/src/clean_document/remove_attributes.ts b/src/clean_document/remove_attributes.ts index 20e7eef..b24fb18 100644 --- a/src/clean_document/remove_attributes.ts +++ b/src/clean_document/remove_attributes.ts @@ -1,31 +1,66 @@ -import { Step } from '../step'; +import { Step } from "../step"; -const DESCRIPTION = 'Removing unused element attributes'; +const DESCRIPTION = "Removing unused element attributes"; -const STYLE_ATTRIBUTES_TO_REMOVE = ['style', 'class']; +const STYLE_ATTRIBUTES_TO_REMOVE = ["style", "class"]; const WINDOW_EVENT_ATTRIBUTES_TO_REMOVE = [ - 'onafterprint', 'onbeforeprint', 'onbeforeunload', 'onerror', 'onhashchange', - 'onload', 'onmessage', 'onoffline', 'ononline', 'onpagehide', 'onpageshow', - 'onpopstate', 'onresize', 'onstorage', 'onunload', + "onafterprint", + "onbeforeprint", + "onbeforeunload", + "onerror", + "onhashchange", + "onload", + "onmessage", + "onoffline", + "ononline", + "onpagehide", + "onpageshow", + "onpopstate", + "onresize", + "onstorage", + "onunload", ]; const FORM_EVENTS_ATTRIBUTES_TO_REMOVE = [ - 'onblur', 'onchange', 'oncontextmenu', 'onfocus', 'oninput', 'oninvalid', - 'onreset', 'onsearch', 'onselect', 'onsubmit', + "onblur", + "onchange", + "oncontextmenu", + "onfocus", + "oninput", + "oninvalid", + "onreset", + "onsearch", + "onselect", + "onsubmit", ]; const KEYBOARD_EVENTS_ATTRIBUTES_TO_REMOVE = [ - 'onkeydown', 'onkeypress', 'onkeyup', + "onkeydown", + "onkeypress", + "onkeyup", ]; const MOUSE_EVENTS_ATTRIBUTES_TO_REMOVE = [ - 'onclick', 'ondblclick', 'onmousedown', 'onmousemove', 'onmouseout', - 'onmouseover', 'onmouseup', 'onmousewheel', 'onwheel', + "onclick", + "ondblclick", + "onmousedown", + "onmousemove", + "onmouseout", + "onmouseover", + "onmouseup", + "onmousewheel", + "onwheel", ]; const DRAG_EVENTS_ATTRIBUTES_TO_REMOVE = [ - 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', - 'ondragstart', 'ondrop', 'onscroll', + "ondrag", + "ondragend", + "ondragenter", + "ondragleave", + "ondragover", + "ondragstart", + "ondrop", + "onscroll", ]; -const CLIPBOARD_EVENTS_ATTRIBUTE_TO_REMOVE = ['oncopy', 'oncut', 'onpaste']; -const MISC_EVENTS_TO_REMOVE = ['ontoggle']; +const CLIPBOARD_EVENTS_ATTRIBUTE_TO_REMOVE = ["oncopy", "oncut", "onpaste"]; +const MISC_EVENTS_TO_REMOVE = ["ontoggle"]; const EVENTS_ATTRIBUTES_TO_REMOVE = [ ...WINDOW_EVENT_ATTRIBUTES_TO_REMOVE, ...FORM_EVENTS_ATTRIBUTES_TO_REMOVE, @@ -37,26 +72,62 @@ const EVENTS_ATTRIBUTES_TO_REMOVE = [ ]; const INTERATIVE_ATTRIBUTES_TO_REMOVE = [ - 'contextmenu', 'draggable', 'tabindex', 'for', 'autocomplete', 'capture', - 'contenteditable', 'crossorigin', 'dirname', 'enterkeyhint', 'form', - 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', - 'inputmode', 'list', 'maxlength', 'minlength', 'max', 'min', 'novalidate', - 'pattern', 'readonly', 'required', 'spellcheck', 'step', 'usemap', - 'autofocus', + "contextmenu", + "draggable", + "tabindex", + "for", + "autocomplete", + "capture", + "contenteditable", + "crossorigin", + "dirname", + "enterkeyhint", + "form", + "formaction", + "formenctype", + "formmethod", + "formnovalidate", + "formtarget", + "inputmode", + "list", + "maxlength", + "minlength", + "max", + "min", + "novalidate", + "pattern", + "readonly", + "required", + "spellcheck", + "step", + "usemap", + "autofocus", ]; -const STYLE_BREAK = ['background', 'bgcolor', 'border', 'width', 'heigth']; +const STYLE_BREAK = ["background", "bgcolor", "border", "width", "heigth"]; const FORM_SUBMISSION = [ - 'accept', 'accept-charset', 'action', 'enctype', 'method', + "accept", + "accept-charset", + "action", + "enctype", + "method", ]; const USELESS = [ - 'loading', 'ping', 'slot', 'sizes', 'decoding', 'crossorigin', - 'elementtiming', 'fetchpriority', 'referrerpolicy', 'sizes', + "loading", + "ping", + "slot", + "sizes", + "decoding", + "crossorigin", + "elementtiming", + "fetchpriority", + "referrerpolicy", + "sizes", ]; -const OFFLINE = ['srcset', 'attributionsrc']; +const OFFLINE = ["srcset", "attributionsrc"]; const ATTRIBUTES_TO_REMOVE = [ ...STYLE_ATTRIBUTES_TO_REMOVE, @@ -68,7 +139,6 @@ const ATTRIBUTES_TO_REMOVE = [ ...OFFLINE, ]; - function removeAttributes(htmlDoc: HTMLDocument) { for (const attribute of ATTRIBUTES_TO_REMOVE) { const elements = htmlDoc.querySelectorAll(`[${attribute}]`); diff --git a/src/clean_document/remove_comments.ts b/src/clean_document/remove_comments.ts index 3c9b457..3784dd4 100644 --- a/src/clean_document/remove_comments.ts +++ b/src/clean_document/remove_comments.ts @@ -1,7 +1,6 @@ -import { Step } from '../step'; - -const DESCRIPTION = 'Removing all HTML comments'; +import { Step } from "../step"; +const DESCRIPTION = "Removing all HTML comments"; function removeAllComments(htmlDoc: HTMLDocument) { function filterNode() { @@ -12,7 +11,7 @@ function removeAllComments(htmlDoc: HTMLDocument) { const iterator = htmlDoc.createNodeIterator( htmlDoc.documentElement, NodeFilter.SHOW_COMMENT, - filterNode + filterNode, ); let node: Comment; diff --git a/src/clean_document/remove_data_attributes.ts b/src/clean_document/remove_data_attributes.ts index 5a84f36..1ec21f3 100644 --- a/src/clean_document/remove_data_attributes.ts +++ b/src/clean_document/remove_data_attributes.ts @@ -1,13 +1,12 @@ -import { Step } from '../step'; - -const DESCRIPTION = 'Removing custom attributes'; +import { Step } from "../step"; +const DESCRIPTION = "Removing custom attributes"; function removeDataAttributes(htmlDoc: HTMLDocument) { function removeDataAttributesElement(element: Element) { const attributes = []; for (const attribute of Array.from(element.attributes)) { - if (attribute.name.indexOf('data-') === 0) { + if (attribute.name.indexOf("data-") === 0) { attributes.push(attribute); } } @@ -20,7 +19,10 @@ function removeDataAttributes(htmlDoc: HTMLDocument) { visitAllElements(htmlDoc, removeDataAttributesElement); } -function visitAllElements(htmlDoc: HTMLDocument, callback: (element: Element) => void) { +function visitAllElements( + htmlDoc: HTMLDocument, + callback: (element: Element) => void, +) { visitElement(htmlDoc.documentElement, callback); } diff --git a/src/clean_document/remove_elements.ts b/src/clean_document/remove_elements.ts index a19d4ec..53e44ec 100644 --- a/src/clean_document/remove_elements.ts +++ b/src/clean_document/remove_elements.ts @@ -1,21 +1,55 @@ -import { Step } from '../step'; +import { Step } from "../step"; -const DESCRIPTION = 'Removing unused HTML elements'; +const DESCRIPTION = "Removing unused HTML elements"; const EMBEDDED_ELEMENTS_TO_REMOVE = [ - 'map', 'area', 'embed', 'object', 'video', 'audio', 'iframe', 'canvas', - 'applet', 'frameset', 'track', 'portal', 'source', 'frame', 'param', 'shadow', + "map", + "area", + "embed", + "object", + "video", + "audio", + "iframe", + "canvas", + "applet", + "frameset", + "track", + "portal", + "source", + "frame", + "param", + "shadow", ]; const FORM_ELEMENTS_TO_REMOVE = [ - 'button', 'input', 'textarea', 'select', 'output', 'datalist', 'keygen', - 'optgroup', 'option', + "button", + "input", + "textarea", + "select", + "output", + "datalist", + "keygen", + "optgroup", + "option", ]; const METADATA_SCRIPT_ELEMENTS_TO_REMOVE = [ - 'base', 'script', 'meta', 'link', 'style', 'template', 'slot', - 'basefont', 'font', + "base", + "script", + "meta", + "link", + "style", + "template", + "slot", + "basefont", + "font", +]; +const INTERACTIVE_ELEMENTS_TO_REMOVE = ["menu", "command", "nav", "menuitem"]; +const NOT_SUPPORTED_ELEMENTS_TO_REMOVE = [ + "math", + "svg", + "col", + "colgroup", + "dialog", ]; -const INTERACTIVE_ELEMENTS_TO_REMOVE = ['menu', 'command', 'nav', 'menuitem']; -const NOT_SUPPORTED_ELEMENTS_TO_REMOVE = ['math', 'svg', 'col', 'colgroup', 'dialog']; const ELEMENTS_TO_REMOVE = [ ...EMBEDDED_ELEMENTS_TO_REMOVE, diff --git a/src/clean_document/remove_empty_elements.ts b/src/clean_document/remove_empty_elements.ts index e3d7a57..b6c8398 100644 --- a/src/clean_document/remove_empty_elements.ts +++ b/src/clean_document/remove_empty_elements.ts @@ -1,13 +1,50 @@ -import { Step } from '../step'; +import { Step } from "../step"; -const DESCRIPTION = 'Removing empty HTML elements'; +const DESCRIPTION = "Removing empty HTML elements"; const TAGS_CAN_REMOVE = [ - 'SPAN', 'ABBR', 'CITE', 'EM', 'I', 'B', 'SUB', 'SUP', 'SMALL', 'STRONG', - 'MARK', 'DEL', 'S', 'CODE', 'P', 'OL', 'UL', 'LI', 'DIV', 'PRE', 'BLOCKQUOTE', - 'LABEL', 'ASIDE', 'ADDRESS', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'MAIN', - 'SECTION', 'HEADER', 'ARTICLE', 'FOOTER', 'SUMMARY', 'DETAILS', 'TABLE', - 'CAPTION', 'THEAD', 'TBODY', 'TFOOT', + "SPAN", + "ABBR", + "CITE", + "EM", + "I", + "B", + "SUB", + "SUP", + "SMALL", + "STRONG", + "MARK", + "DEL", + "S", + "CODE", + "P", + "OL", + "UL", + "LI", + "DIV", + "PRE", + "BLOCKQUOTE", + "LABEL", + "ASIDE", + "ADDRESS", + "H1", + "H2", + "H3", + "H4", + "H5", + "H6", + "MAIN", + "SECTION", + "HEADER", + "ARTICLE", + "FOOTER", + "SUMMARY", + "DETAILS", + "TABLE", + "CAPTION", + "THEAD", + "TBODY", + "TFOOT", ]; function removeEmptyElements(htmlDoc: HTMLDocument) { @@ -19,7 +56,7 @@ function removeEmptyElements(htmlDoc: HTMLDocument) { const iterator = htmlDoc.createNodeIterator( htmlDoc.documentElement, NodeFilter.SHOW_ELEMENT, - filterNode + filterNode, ); let node: HTMLElement; @@ -43,8 +80,7 @@ function removeIfEmptyElement(element: HTMLElement) { function canRemoveElement(element: HTMLElement) { return ( - (TAGS_CAN_REMOVE.indexOf(element.tagName) !== -1) - && (!element.innerHTML.trim()) + TAGS_CAN_REMOVE.indexOf(element.tagName) !== -1 && !element.innerHTML.trim() ); } diff --git a/src/clean_document/remove_empty_svg.ts b/src/clean_document/remove_empty_svg.ts index b03ab19..b85ad3f 100644 --- a/src/clean_document/remove_empty_svg.ts +++ b/src/clean_document/remove_empty_svg.ts @@ -1,27 +1,28 @@ -import { Step } from '../step'; +import { Step } from "../step"; - -const DESCRIPTION = 'Remove empty SVGs'; +const DESCRIPTION = "Remove empty SVGs"; function removeEmptySVGs(htmlDoc: HTMLDocument) { - const svgs = Array.from(htmlDoc.querySelectorAll('svg')); - svgs.filter(isEmptySvg).forEach(svg => svg.remove()); + const svgs = Array.from(htmlDoc.querySelectorAll("svg")); + svgs.filter(isEmptySvg).forEach((svg) => svg.remove()); const parser = new DOMParser(); - const images = Array.from(htmlDoc.querySelectorAll('img[src]')); + const images = Array.from(htmlDoc.querySelectorAll("img[src]")); images - .filter(image => image.getAttribute('src')!.startsWith('data:image/svg+xml')) - .forEach(image => { - const src = image.getAttribute('src')!; - fetch(src) - .then(response => response.text()) - .then(content => parser.parseFromString(content, 'text/xml')) - .then(svg => { - if (isEmptySvg(svg)) { - image.remove(); - } - }); - }); + .filter((image) => + image.getAttribute("src")!.startsWith("data:image/svg+xml"), + ) + .forEach((image) => { + const src = image.getAttribute("src")!; + fetch(src) + .then((response) => response.text()) + .then((content) => parser.parseFromString(content, "text/xml")) + .then((svg) => { + if (isEmptySvg(svg)) { + image.remove(); + } + }); + }); } export function isEmptySvg(svg: XMLDocument | SVGElement) { diff --git a/src/clean_document/remove_hidden_elements.ts b/src/clean_document/remove_hidden_elements.ts index ec47ddf..7cab621 100644 --- a/src/clean_document/remove_hidden_elements.ts +++ b/src/clean_document/remove_hidden_elements.ts @@ -1,7 +1,6 @@ -import { Step } from '../step'; - -const DESCRIPTION = 'Removing HTML hidden elements'; +import { Step } from "../step"; +const DESCRIPTION = "Removing HTML hidden elements"; function removeHiddenElements(htmlDoc: HTMLDocument) { const hiddenElements = htmlDoc.querySelectorAll(`[hidden]`); diff --git a/src/clean_document/remove_whitespaces.ts b/src/clean_document/remove_whitespaces.ts index 42cc082..463def6 100644 --- a/src/clean_document/remove_whitespaces.ts +++ b/src/clean_document/remove_whitespaces.ts @@ -1,7 +1,6 @@ -import { Step } from '../step'; - -const DESCRIPTION = 'Removing extra whitespaces'; +import { Step } from "../step"; +const DESCRIPTION = "Removing extra whitespaces"; function removeExtraWhitespacesFromDocument(htmlDoc: HTMLDocument) { function filterNode() { @@ -48,21 +47,22 @@ function mergeTextNodesElement(element: Element) { } function removeExtraWhitespaces(textNode: Text) { - if (!canRemoveWhitespaces(textNode)) { - return; - } + if (!canRemoveWhitespaces(textNode)) { + return; + } - textNode.nodeValue = textNode.nodeValue!.replace(/\r/g, '') - .replace(/\t/g, ' ') - .replace(/ +/g, ' ') - .replace(/\n[\n ]+/g, '\n') - .replace(/ +\n/g, '\n'); + textNode.nodeValue = textNode + .nodeValue!.replace(/\r/g, "") + .replace(/\t/g, " ") + .replace(/ +/g, " ") + .replace(/\n[\n ]+/g, "\n") + .replace(/ +\n/g, "\n"); } function canRemoveWhitespaces(textNode: Text) { - const parentTagName = textNode.parentElement!.tagName; + const parentTagName = textNode.parentElement!.tagName; - return ['PRE', 'CODE'].indexOf(parentTagName) == -1; + return ["PRE", "CODE"].indexOf(parentTagName) == -1; } export default new Step(DESCRIPTION, removeExtraWhitespacesFromDocument); diff --git a/src/convert_text_dom.ts b/src/convert_text_dom.ts index 2e06cf3..4e652e6 100644 --- a/src/convert_text_dom.ts +++ b/src/convert_text_dom.ts @@ -1,13 +1,11 @@ -import { Step } from './step'; - - -const DESCRIPTION = 'Converting downloaded text to DOM'; +import { Step } from "./step"; +const DESCRIPTION = "Converting downloaded text to DOM"; function convertTextToDOM(content: string) { const parser = new DOMParser(); - return parser.parseFromString(content, 'text/html'); + return parser.parseFromString(content, "text/html"); } export default new Step(DESCRIPTION, convertTextToDOM); diff --git a/src/create_epub.ts b/src/create_epub.ts index 6329627..20f95af 100644 --- a/src/create_epub.ts +++ b/src/create_epub.ts @@ -1,10 +1,9 @@ -import jEpub from 'jepub'; +import jEpub from "jepub"; -import { Step } from './step'; -import { Logger } from './logger'; - -const DESCRIPTION = 'Creating EPUB file'; +import { Step } from "./step"; +import { Logger } from "./logger"; +const DESCRIPTION = "Creating EPUB file"; export interface SplittedContent { title: string; @@ -25,35 +24,43 @@ async function createEPUB( logger: Logger, contents: SplittedContent[], metadata: Metadata, - images: {id: string, blob: Blob, attributes: Record}[], -): Promise<{title: string, epub: Blob}> { + images: { id: string; blob: Blob; attributes: Record }[], +): Promise<{ title: string; epub: Blob }> { const jepub = new jEpub(); - jepub.init({ i18n: 'en', ...metadata }); + jepub.init({ i18n: "en", ...metadata }); jepub.uuid(metadata.uuid); jepub.date(metadata.date); - images.forEach(image => jepub.image(image.blob, image.id, image.attributes)); + images.forEach((image) => + jepub.image(image.blob, image.id, image.attributes), + ); for (const content of contents) { jepub.add(content.title, content.content); } - const epub = await jepub.generate('blob', (metadata: { percent: number, currentFile: string }) => { - logger.log(`progression: ${metadata.percent.toFixed(2)} %`); + const epub = (await jepub.generate( + "blob", + (metadata: { percent: number; currentFile: string }) => { + logger.log(`progression: ${metadata.percent.toFixed(2)} %`); - if (metadata.currentFile) { - logger.log(`current file = ${metadata.currentFile}`); - } - }) as Blob; + if (metadata.currentFile) { + logger.log(`current file = ${metadata.currentFile}`); + } + }, + )) as Blob; - return {title: metadata.title, epub}; + return { title: metadata.title, epub }; } export default function createEpubStep(logger: Logger) { - return new Step(DESCRIPTION, ( - contents: SplittedContent[], - metadata: Metadata, - images: {id: string, blob: Blob, attributes: Record}[], - ) => createEPUB(logger, contents, metadata, images)); + return new Step( + DESCRIPTION, + ( + contents: SplittedContent[], + metadata: Metadata, + images: { id: string; blob: Blob; attributes: Record }[], + ) => createEPUB(logger, contents, metadata, images), + ); } diff --git a/src/fix_links/index.ts b/src/fix_links/index.ts index 15623f9..1a90c37 100644 --- a/src/fix_links/index.ts +++ b/src/fix_links/index.ts @@ -1,25 +1,29 @@ -import replaceUrlLinks from './replace_url_links'; -import setExternalLinksBlank from './set_external_links_blank'; -import removeBrokenAnchorLinks from './remove_broken_anchor_links'; -import { Step, SubProcessStep, Process } from '../step'; -import { SplittedElement } from '../split_main_content'; +import replaceUrlLinks from "./replace_url_links"; +import setExternalLinksBlank from "./set_external_links_blank"; +import removeBrokenAnchorLinks from "./remove_broken_anchor_links"; +import { Step, SubProcessStep, Process } from "../step"; +import { SplittedElement } from "../split_main_content"; -const DESCRIPTION = 'Fix links'; +const DESCRIPTION = "Fix links"; - -function buildFixLinksProcess(splitedContents: SplittedElement[], originUrl: string): Process { - const splitedContentsStep = new Step( - 'Splitted elements step', - () => splitedContents.map(splitedContent => splitedContent.element) +function buildFixLinksProcess( + splitedContents: SplittedElement[], + originUrl: string, +): Process { + const splitedContentsStep = new Step("Splitted elements step", () => + splitedContents.map((splitedContent) => splitedContent.element), ); - const originUrlStep = new Step('Origin URL step', () => originUrl); + const originUrlStep = new Step("Origin URL step", () => originUrl); return new Process([ - {step: splitedContentsStep}, - {step: originUrlStep}, - {step: replaceUrlLinks, dependencies: [splitedContentsStep, originUrlStep]}, - {step: setExternalLinksBlank, dependencies: [replaceUrlLinks]}, - {step: removeBrokenAnchorLinks, dependencies: [replaceUrlLinks]}, + { step: splitedContentsStep }, + { step: originUrlStep }, + { + step: replaceUrlLinks, + dependencies: [splitedContentsStep, originUrlStep], + }, + { step: setExternalLinksBlank, dependencies: [replaceUrlLinks] }, + { step: removeBrokenAnchorLinks, dependencies: [replaceUrlLinks] }, ]); } diff --git a/src/fix_links/remove_broken_anchor_links.ts b/src/fix_links/remove_broken_anchor_links.ts index da6c2e8..31f1524 100644 --- a/src/fix_links/remove_broken_anchor_links.ts +++ b/src/fix_links/remove_broken_anchor_links.ts @@ -1,15 +1,17 @@ -import { Step } from '../step'; -import replaceElementWithStructure from '../replace_element'; +import { Step } from "../step"; +import replaceElementWithStructure from "../replace_element"; -const DESCRIPTION = 'Remove broken anchor links'; +const DESCRIPTION = "Remove broken anchor links"; function removeBrokenAnchorLinks(splitedContents: Element[]) { for (let i = 0, length = splitedContents.length; i < length; i++) { const splitedContent = splitedContents[i]; - const anchorLinkElements = Array.from(splitedContent.querySelectorAll('a[href^="#"]')); + const anchorLinkElements = Array.from( + splitedContent.querySelectorAll('a[href^="#"]'), + ); for (const anchorLinkElement of anchorLinkElements) { - const anchor = anchorLinkElement.getAttribute('href')!.substring(1); + const anchor = anchorLinkElement.getAttribute("href")!.substring(1); const indexContent = indexOfAnchor(splitedContents, anchor); if (indexContent === -1) { @@ -26,7 +28,9 @@ function removeBrokenAnchorLinks(splitedContents: Element[]) { function indexOfAnchor(splitedContents: Element[], anchor: string) { for (let i = 0, length = splitedContents.length; i < length; i++) { const splitedContent = splitedContents[i]; - const anchorElement = splitedContent.querySelector(`[id="${anchor}"],a[name="${anchor}"]`); + const anchorElement = splitedContent.querySelector( + `[id="${anchor}"],a[name="${anchor}"]`, + ); if (anchorElement !== null) { return i; @@ -37,16 +41,16 @@ function indexOfAnchor(splitedContents: Element[], anchor: string) { } function replaceLinkBySpan(anchorLinkElement: Element) { - const span = document.createElement('span'); + const span = document.createElement("span"); replaceElementWithStructure(anchorLinkElement, span); - span.removeAttribute('href'); + span.removeAttribute("href"); } function fixAnchorLink(anchorLinkElement: Element, indexContent: number) { - const anchor = anchorLinkElement.getAttribute('href'); + const anchor = anchorLinkElement.getAttribute("href"); const newHref = `page-${indexContent}.html${anchor}`; - anchorLinkElement.setAttribute('href', newHref); + anchorLinkElement.setAttribute("href", newHref); } export default new Step(DESCRIPTION, removeBrokenAnchorLinks); diff --git a/src/fix_links/replace_url_links.ts b/src/fix_links/replace_url_links.ts index 7170b0b..2f01ccf 100644 --- a/src/fix_links/replace_url_links.ts +++ b/src/fix_links/replace_url_links.ts @@ -1,16 +1,18 @@ -import { Step } from '../step'; +import { Step } from "../step"; -const DESCRIPTION = 'Replace URL links'; +const DESCRIPTION = "Replace URL links"; function fixLinks(splitedContents: Element[], originUrl: string) { - const linksElements = splitedContents.flatMap(splitedContent => Array.from(splitedContent.querySelectorAll('a[href]'))); + const linksElements = splitedContents.flatMap((splitedContent) => + Array.from(splitedContent.querySelectorAll("a[href]")), + ); for (const linkElement of linksElements) { - let newUrl = linkElement.getAttribute('href')!; + let newUrl = linkElement.getAttribute("href")!; newUrl = convertUrlToAbsolute(newUrl, originUrl); newUrl = retrieveAnchorIfLocalUrl(newUrl, originUrl); - linkElement.setAttribute('href', newUrl); + linkElement.setAttribute("href", newUrl); } return splitedContents; @@ -22,7 +24,7 @@ function convertUrlToAbsolute(href: string, originUrl: string) { function retrieveAnchorIfLocalUrl(href: string, originUrl: string) { if (isSamePage(originUrl, href)) { - const anchor = href.split('#')[1]; + const anchor = href.split("#")[1]; if (anchor) { return `#${anchor}`; @@ -33,8 +35,8 @@ function retrieveAnchorIfLocalUrl(href: string, originUrl: string) { } function isSamePage(originUrl: string, url: string) { - const originUrlWithoutAnchor = originUrl.split('#')[0]; - const urlWithoutAnchor = url.split('#')[0]; + const originUrlWithoutAnchor = originUrl.split("#")[0]; + const urlWithoutAnchor = url.split("#")[0]; return originUrlWithoutAnchor === urlWithoutAnchor; } diff --git a/src/fix_links/set_external_links_blank.ts b/src/fix_links/set_external_links_blank.ts index 040dc7a..78b1f48 100644 --- a/src/fix_links/set_external_links_blank.ts +++ b/src/fix_links/set_external_links_blank.ts @@ -1,12 +1,18 @@ -import { Step } from '../step'; +import { Step } from "../step"; -const DESCRIPTION = 'Set external links to open in a new tab'; +const DESCRIPTION = "Set external links to open in a new tab"; function setExternalLinksBlank(splitedContents: Element[]) { - const linksElements = splitedContents.flatMap(splitedContent => Array.from(splitedContent.querySelectorAll('a[href^="http://"], a[href^="https://"]'))); + const linksElements = splitedContents.flatMap((splitedContent) => + Array.from( + splitedContent.querySelectorAll( + 'a[href^="http://"], a[href^="https://"]', + ), + ), + ); for (const linkElement of linksElements) { - linkElement.setAttribute('target', '_blank'); + linkElement.setAttribute("target", "_blank"); } return splitedContents; diff --git a/src/get_main_content.ts b/src/get_main_content.ts index 63a9294..ad282c8 100644 --- a/src/get_main_content.ts +++ b/src/get_main_content.ts @@ -1,10 +1,9 @@ -import { Step } from './step'; - -const DESCRIPTION = 'Choosing the main content from HTML document'; +import { Step } from "./step"; +const DESCRIPTION = "Choosing the main content from HTML document"; function getContent(htmlDoc: HTMLDocument) { - const mainArticleElement = htmlDoc.querySelectorAll('main article'); + const mainArticleElement = htmlDoc.querySelectorAll("main article"); if (mainArticleElement.length === 1) { return mainArticleElement[0]; } @@ -14,7 +13,7 @@ function getContent(htmlDoc: HTMLDocument) { return mainElement; } - const articleElement = htmlDoc.querySelector('article'); + const articleElement = htmlDoc.querySelector("article"); if (articleElement) { return articleElement; } diff --git a/src/get_metadata.ts b/src/get_metadata.ts index c05a8f1..f3e983d 100644 --- a/src/get_metadata.ts +++ b/src/get_metadata.ts @@ -1,7 +1,6 @@ -import { Step } from './step'; - -const DESCRIPTION = 'Retrieving metadata from HTML document'; +import { Step } from "./step"; +const DESCRIPTION = "Retrieving metadata from HTML document"; function getMetadata(htmlDoc: HTMLDocument, url: string) { return { @@ -12,7 +11,7 @@ function getMetadata(htmlDoc: HTMLDocument, url: string) { uuid: url, description: getDescription(htmlDoc), tags: getTags(htmlDoc), - } + }; } function getTitle(htmlDoc: HTMLDocument, url: string) { @@ -31,35 +30,35 @@ function getTitle(htmlDoc: HTMLDocument, url: string) { function getDate(htmlDoc: HTMLDocument) { const datePublishedStructuredMetadata = htmlDoc.querySelector( - 'meta[itemprop="datePublished"][content]' + 'meta[itemprop="datePublished"][content]', ); if (datePublishedStructuredMetadata) { try { - return new Date(datePublishedStructuredMetadata.getAttribute('content')!); + return new Date(datePublishedStructuredMetadata.getAttribute("content")!); } catch (_) { // continue regardless of error } } const startDate = htmlDoc.querySelector( - 'time[itemprop="startDate"][datetime]' + 'time[itemprop="startDate"][datetime]', ); if (startDate) { try { - return new Date(startDate.getAttribute('datetime')!); + return new Date(startDate.getAttribute("datetime")!); } catch (_) { // continue regardless of error } } - const contentDate = getContentFromMetatags( - htmlDoc, - [ - 'article:published_time', 'article:modified_time', 'book:release_date', - 'og:article:published_time', 'og:article:modified_time', - 'og:book:release_date', - ], - ); + const contentDate = getContentFromMetatags(htmlDoc, [ + "article:published_time", + "article:modified_time", + "book:release_date", + "og:article:published_time", + "og:article:modified_time", + "og:book:release_date", + ]); if (contentDate) { try { return new Date(contentDate); @@ -72,16 +71,16 @@ function getDate(htmlDoc: HTMLDocument) { } function getAuthor(htmlDoc: HTMLDocument) { - const contentAuthor = getContentFromMetatags(htmlDoc, ['author']); + const contentAuthor = getContentFromMetatags(htmlDoc, ["author"]); if (contentAuthor) { return contentAuthor; } const authorStructuredMetadata = htmlDoc.querySelector( - '[itemprop="author"] meta[itemprop="name"][content]' + '[itemprop="author"] meta[itemprop="name"][content]', ); if (authorStructuredMetadata) { - return authorStructuredMetadata.getAttribute('content'); + return authorStructuredMetadata.getAttribute("content"); } const authorStructured = htmlDoc.querySelector('[itemprop="author"]'); @@ -89,35 +88,41 @@ function getAuthor(htmlDoc: HTMLDocument) { return authorStructured.textContent; } - return ''; + return ""; } function getPublisher(htmlDoc: HTMLDocument) { const publisherStructuredMetadata = htmlDoc.querySelector( - '[itemprop="publisher"] meta[itemprop="name"][content]' + '[itemprop="publisher"] meta[itemprop="name"][content]', ); if (publisherStructuredMetadata) { - return publisherStructuredMetadata.getAttribute('content'); + return publisherStructuredMetadata.getAttribute("content"); } - return getContentFromMetatags( - htmlDoc, - ['publisher', 'owner', 'copyright', 'og:site_name'], - ); + return getContentFromMetatags(htmlDoc, [ + "publisher", + "owner", + "copyright", + "og:site_name", + ]); } function getDescription(htmlDoc: HTMLDocument) { - return getContentFromMetatags( - htmlDoc, - ['description', 'og:description', 'subtitle', 'abstract'], - ) || ''; + return ( + getContentFromMetatags(htmlDoc, [ + "description", + "og:description", + "subtitle", + "abstract", + ]) || "" + ); } function getTags(htmlDoc: HTMLDocument) { - const content = getContentFromMetatags( - htmlDoc, - ['news_keywords', 'keywords'], - ); + const content = getContentFromMetatags(htmlDoc, [ + "news_keywords", + "keywords", + ]); if (content) { return content.split(/\s*,\s*/); @@ -134,7 +139,7 @@ function getContentFromMetatags( const metaElement = htmlDoc.querySelector(`meta[name="${name}"][content]`); if (metaElement) { - const content = metaElement.getAttribute('content')!.trim(); + const content = metaElement.getAttribute("content")!.trim(); if (content) { return content; diff --git a/src/index.ts b/src/index.ts index 37b4297..869287e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,14 @@ -import convertTextToDOM from './convert_text_dom'; -import getMetadata from './get_metadata'; -import cleanDocument from './clean_document'; -import replaceElements from './replace_elements'; -import getMainContent from './get_main_content'; -import splitContentByHeadings, { SplittedElement } from './split_main_content'; -import loadImagesStepFactory from './load_images'; -import createEpubStepFactory from './create_epub'; -import fixLinks from './fix_links'; -import { Step, Process } from './step'; -import { Logger } from './logger'; - +import convertTextToDOM from "./convert_text_dom"; +import getMetadata from "./get_metadata"; +import cleanDocument from "./clean_document"; +import replaceElements from "./replace_elements"; +import getMainContent from "./get_main_content"; +import splitContentByHeadings, { SplittedElement } from "./split_main_content"; +import loadImagesStepFactory from "./load_images"; +import createEpubStepFactory from "./create_epub"; +import fixLinks from "./fix_links"; +import { Step, Process } from "./step"; +import { Logger } from "./logger"; export default async function convertDocumentToEPub( url: string, @@ -19,28 +18,41 @@ export default async function convertDocumentToEPub( callbackLength: (length: number) => void, logger: Logger, ) { - const urlStep = new Step('URL recover step', () => url); + const urlStep = new Step("URL recover step", () => url); const htmlContentStep = new Step(`Loading "${url}"`, () => htmlContent); const convertSplitedContentInHTMLContentStep = new Step( - 'Convert splited content in HTML content', + "Convert splited content in HTML content", convertSplitedContentInHTMLContent, ); const loadImages = loadImagesStepFactory(loadImageFrom); const createEPUB = createEpubStepFactory(logger); const convertDocumentProcess = new Process([ - {step: urlStep}, - {step: htmlContentStep}, - {step: convertTextToDOM, dependencies: [htmlContentStep]}, - {step: getMetadata, dependencies: [convertTextToDOM, urlStep]}, - {step: cleanDocument, dependencies: [convertTextToDOM]}, - {step: replaceElements, dependencies: [convertTextToDOM]}, - {step: getMainContent, dependencies: [convertTextToDOM]}, - {step: loadImages, dependencies: [getMainContent, urlStep]}, - {step: splitContentByHeadings, dependencies: [getMainContent, getMetadata]}, - {step: fixLinks, dependencies: [splitContentByHeadings, urlStep]}, - {step: convertSplitedContentInHTMLContentStep, dependencies: [splitContentByHeadings]}, - {step: createEPUB, dependencies: [convertSplitedContentInHTMLContentStep, getMetadata, loadImages]}, + { step: urlStep }, + { step: htmlContentStep }, + { step: convertTextToDOM, dependencies: [htmlContentStep] }, + { step: getMetadata, dependencies: [convertTextToDOM, urlStep] }, + { step: cleanDocument, dependencies: [convertTextToDOM] }, + { step: replaceElements, dependencies: [convertTextToDOM] }, + { step: getMainContent, dependencies: [convertTextToDOM] }, + { step: loadImages, dependencies: [getMainContent, urlStep] }, + { + step: splitContentByHeadings, + dependencies: [getMainContent, getMetadata], + }, + { step: fixLinks, dependencies: [splitContentByHeadings, urlStep] }, + { + step: convertSplitedContentInHTMLContentStep, + dependencies: [splitContentByHeadings], + }, + { + step: createEPUB, + dependencies: [ + convertSplitedContentInHTMLContentStep, + getMetadata, + loadImages, + ], + }, ]); callbackLength(convertDocumentProcess.getLength()); @@ -51,7 +63,7 @@ export default async function convertDocumentToEPub( function convertSplitedContentInHTMLContent( splitedContents: SplittedElement[], ) { - return splitedContents.map(splitedContent => ({ + return splitedContents.map((splitedContent) => ({ title: splitedContent.title, content: getHtmlContent(splitedContent.element), })); @@ -62,14 +74,15 @@ function getHtmlContent(element: Element) { const domParser = new DOMParser(); const htmlCode = xmlSerializer.serializeToString(element); - const xhtmlElement = domParser.parseFromString(htmlCode, 'text/html'); + const xhtmlElement = domParser.parseFromString(htmlCode, "text/html"); const xhtmlCode = xmlSerializer.serializeToString(xhtmlElement); - const xhtmlDocument = domParser.parseFromString(xhtmlCode, 'text/html'); + const xhtmlDocument = domParser.parseFromString(xhtmlCode, "text/html"); return replaceCommentsImagesByImages(xhtmlDocument.body.innerHTML); } function replaceCommentsImagesByImages(content: string) { - return content.replace(//g, '] %>'); + return content + .replace(//g, "] %>"); } diff --git a/src/load_images.ts b/src/load_images.ts index 0410cd1..b5e994b 100644 --- a/src/load_images.ts +++ b/src/load_images.ts @@ -1,9 +1,9 @@ -import { Step } from './step'; -import { isEmptySvg } from './clean_document/remove_empty_svg'; +import { Step } from "./step"; +import { isEmptySvg } from "./clean_document/remove_empty_svg"; -import NO_IMAGE_DATA_URL from '../img/no-image.png'; +import NO_IMAGE_DATA_URL from "../img/no-image.png"; -const DESCRIPTION = 'Loading images'; +const DESCRIPTION = "Loading images"; const parser = new DOMParser(); interface LoadedImage { @@ -12,37 +12,46 @@ interface LoadedImage { attributes: Record; } - -export default function loadImagesStepFactory(loadImageFrom: (url: string) => Promise) { +export default function loadImagesStepFactory( + loadImageFrom: (url: string) => Promise, +) { async function loadImages(mainElement: Element, url: string) { const loadImage = memoizedLoadImage(); - const replaceImageSrcByAbsoluteSrc = replaceImageSrcByPageURLAbsoluteSrc(url); + const replaceImageSrcByAbsoluteSrc = + replaceImageSrcByPageURLAbsoluteSrc(url); - const images = getAllImages(mainElement) - .filter(image => !image.getAttribute('src')!.startsWith('data:')); + const images = getAllImages(mainElement).filter( + (image) => !image.getAttribute("src")!.startsWith("data:"), + ); images.forEach(replaceImageSrcByAbsoluteSrc); - const promises = images.map(image => loadImage(image).then(loadedImage => { - replaceImageByID(image, loadedImage); + const promises = images.map((image) => + loadImage(image).then((loadedImage) => { + replaceImageByID(image, loadedImage); - return loadedImage; - })); + return loadedImage; + }), + ); return Promise.allSettled(promises) - .then(settledPromises => settledPromises.filter(promise => promise.status === 'fulfilled').map((promise: PromiseFulfilledResult) => promise.value)) - .then(results => Array.from(new Set(results))); + .then((settledPromises) => + settledPromises + .filter((promise) => promise.status === "fulfilled") + .map((promise: PromiseFulfilledResult) => promise.value), + ) + .then((results) => Array.from(new Set(results))); } function getAllImages(mainElement: Element) { - return Array.from( - mainElement.querySelectorAll('img[src]') - ).filter(element => element.getAttribute('src')!.trim()); + return Array.from(mainElement.querySelectorAll("img[src]")).filter( + (element) => element.getAttribute("src")!.trim(), + ); } function replaceImageSrcByPageURLAbsoluteSrc(pageURL: string) { return (image: Element) => { - const srcURL = image.getAttribute('src')!.trim(); + const srcURL = image.getAttribute("src")!.trim(); const srcAbsoluteURL = new URL(srcURL, pageURL).toString(); - image.setAttribute('src', srcAbsoluteURL); + image.setAttribute("src", srcAbsoluteURL); }; } @@ -56,46 +65,51 @@ export default function loadImagesStepFactory(loadImageFrom: (url: string) => Pr const cache: Record> = {}; return (image: Element) => { - const srcURL = image.getAttribute('src')!; + const srcURL = image.getAttribute("src")!; if (srcURL in cache) { return cache[srcURL]; } - const promise = loadImageFrom(srcURL).then(async (blob) => { - if (!blob.type.startsWith('image/')) { - throw new Error(); - } - - if (blob.type === 'image/svg+xml') { - const svgContent = await blob.text(); - const svg = parser.parseFromString(svgContent, 'text/xml'); - if (isEmptySvg(svg)) { + const promise = loadImageFrom(srcURL) + .then(async (blob) => { + if (!blob.type.startsWith("image/")) { throw new Error(); } - } - - const id = generateHash(srcURL); - const attributes = Array.from(image.attributes).reduce((acc: Record, attribute) => { - if (attribute.nodeValue) { - acc[attribute.nodeName] = attribute.nodeValue; - } else if (attribute.nodeName !== 'alt') { - acc[attribute.nodeName] = attribute.nodeName; - } - return acc; - }, {}); + if (blob.type === "image/svg+xml") { + const svgContent = await blob.text(); + const svg = parser.parseFromString(svgContent, "text/xml"); + if (isEmptySvg(svg)) { + throw new Error(); + } + } - return {id, blob, attributes}; - }).catch(_ => { - return fetch(NO_IMAGE_DATA_URL) - .then(response => response.blob()) - .then(blob => ({id: 'no-image', blob, attributes: {}})); - }); + const id = generateHash(srcURL); + const attributes = Array.from(image.attributes).reduce( + (acc: Record, attribute) => { + if (attribute.nodeValue) { + acc[attribute.nodeName] = attribute.nodeValue; + } else if (attribute.nodeName !== "alt") { + acc[attribute.nodeName] = attribute.nodeName; + } + + return acc; + }, + {}, + ); + + return { id, blob, attributes }; + }) + .catch((_) => { + return fetch(NO_IMAGE_DATA_URL) + .then((response) => response.blob()) + .then((blob) => ({ id: "no-image", blob, attributes: {} })); + }); cache[srcURL] = promise; return promise; - } + }; } return new Step(DESCRIPTION, loadImages); @@ -104,12 +118,12 @@ export default function loadImagesStepFactory(loadImageFrom: (url: string) => Pr function generateHash(str: string) { let hash = 0; - if (str.length === 0) return 'w' + hash.toString(36); + if (str.length === 0) return "w" + hash.toString(36); for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; } - return 'w' + (hash >>> 0).toString(36); + return "w" + (hash >>> 0).toString(36); } diff --git a/src/replace_element.ts b/src/replace_element.ts index 34f7d56..f761c1f 100644 --- a/src/replace_element.ts +++ b/src/replace_element.ts @@ -1,4 +1,7 @@ -export default function replaceElementWithStructure(from: Element, to: Element) { +export default function replaceElementWithStructure( + from: Element, + to: Element, +) { copyAttributes(from, to); transferChildNodes(from, to); (from.parentNode as Element).replaceChild(to, from); diff --git a/src/replace_elements/index.ts b/src/replace_elements/index.ts index ea5d839..1d4a8db 100644 --- a/src/replace_elements/index.ts +++ b/src/replace_elements/index.ts @@ -1,20 +1,19 @@ -import { Step, SubProcessStep, Process } from '../step'; -import reduceHeadingLevelPage from './reduce_heading_level'; -import replaceUnknownElements from './replace_unknown_elements'; -import replaceSimpleElementsTag from './replace_simple_elements_tag'; -import replaceElementsByOtherWithCSS from './replace_elements_by_others_with_css'; - -const DESCRIPTION = 'Replace HTML elements'; +import { Step, SubProcessStep, Process } from "../step"; +import reduceHeadingLevelPage from "./reduce_heading_level"; +import replaceUnknownElements from "./replace_unknown_elements"; +import replaceSimpleElementsTag from "./replace_simple_elements_tag"; +import replaceElementsByOtherWithCSS from "./replace_elements_by_others_with_css"; +const DESCRIPTION = "Replace HTML elements"; function buildReplaceElementsProcess(htmlDoc: HTMLDocument): Process { - const htmlDocStep = new Step('HTML document step', () => htmlDoc); + const htmlDocStep = new Step("HTML document step", () => htmlDoc); return new Process([ - {step: htmlDocStep}, - {step: reduceHeadingLevelPage, dependencies: [htmlDocStep]}, - {step: replaceSimpleElementsTag, dependencies: [htmlDocStep]}, - {step: replaceUnknownElements, dependencies: [htmlDocStep]}, + { step: htmlDocStep }, + { step: reduceHeadingLevelPage, dependencies: [htmlDocStep] }, + { step: replaceSimpleElementsTag, dependencies: [htmlDocStep] }, + { step: replaceUnknownElements, dependencies: [htmlDocStep] }, { step: replaceElementsByOtherWithCSS, dependencies: [ @@ -22,7 +21,7 @@ function buildReplaceElementsProcess(htmlDoc: HTMLDocument): Process { reduceHeadingLevelPage, replaceSimpleElementsTag, replaceUnknownElements, - ] + ], }, ]); } diff --git a/src/replace_elements/reduce_heading_level.ts b/src/replace_elements/reduce_heading_level.ts index 7354d67..75749dc 100644 --- a/src/replace_elements/reduce_heading_level.ts +++ b/src/replace_elements/reduce_heading_level.ts @@ -1,14 +1,14 @@ -import { Step } from '../step'; -import replaceElementWithStructure from '../replace_element'; +import { Step } from "../step"; +import replaceElementWithStructure from "../replace_element"; -const DESCRIPTION = 'Reducing the heading level'; +const DESCRIPTION = "Reducing the heading level"; const HEADING_MAP: Record = { - 'H1': 'h2', - 'H2': 'h3', - 'H3': 'h4', - 'H4': 'h5', - 'H5': 'h6', + H1: "h2", + H2: "h3", + H3: "h4", + H4: "h5", + H5: "h6", }; function reduceHeadingLevelPage(htmlDoc: HTMLDocument) { @@ -16,14 +16,16 @@ function reduceHeadingLevelPage(htmlDoc: HTMLDocument) { return; } - const headings = Array.from(htmlDoc.querySelectorAll('h1, h2, h3, h4, h5, h6')); + const headings = Array.from( + htmlDoc.querySelectorAll("h1, h2, h3, h4, h5, h6"), + ); for (const heading of headings) { reduceHeadingLevel(heading); } } function hasH1Heading(htmlDoc: HTMLDocument) { - return Array.from(htmlDoc.querySelectorAll('h1')).length > 0; + return Array.from(htmlDoc.querySelectorAll("h1")).length > 0; } function reduceHeadingLevel(element: Element) { @@ -34,10 +36,10 @@ function reduceHeadingLevel(element: Element) { function getNewHeadingElementLowerThan(element: Element) { const tagName = element.tagName; - if (tagName === 'H6') { - const newElement = document.createElement('p'); - newElement.setAttribute('role', 'heading'); - newElement.setAttribute('aria-level', '7'); + if (tagName === "H6") { + const newElement = document.createElement("p"); + newElement.setAttribute("role", "heading"); + newElement.setAttribute("aria-level", "7"); return newElement; } diff --git a/src/replace_elements/replace_elements_by_others_with_css.ts b/src/replace_elements/replace_elements_by_others_with_css.ts index 1e528b6..0c06b2f 100644 --- a/src/replace_elements/replace_elements_by_others_with_css.ts +++ b/src/replace_elements/replace_elements_by_others_with_css.ts @@ -1,43 +1,43 @@ -import { Step } from '../step'; -import replaceElementWithStructure from '../replace_element'; +import { Step } from "../step"; +import replaceElementWithStructure from "../replace_element"; type MyCSSProperties = Record; -type MyMap = Record; +type MyMap = Record; const SIMPLE_TAGS: MyMap = { - 'mark': { - tag: 'span', - properties: {'background-color': '#ff0'}, + mark: { + tag: "span", + properties: { "background-color": "#ff0" }, }, - 'u': { - tag: 'span', + u: { + tag: "span", properties: { - 'text-decoration-color': 'red', - 'text-decoration-style': 'wavy', - 'text-decoration-line': 'underline', - 'text-decoration': 'red wavy underline', + "text-decoration-color": "red", + "text-decoration-style": "wavy", + "text-decoration-line": "underline", + "text-decoration": "red wavy underline", }, }, - 'center': { - tag: 'div', - properties: {'text-align': 'center'}, + center: { + tag: "div", + properties: { "text-align": "center" }, }, - 'table': { - tag: 'table', - properties: {'border-collapse': 'collapse'}, + table: { + tag: "table", + properties: { "border-collapse": "collapse" }, }, - 'th': { - tag: 'th', - properties: {'border': '1px solid black'}, + th: { + tag: "th", + properties: { border: "1px solid black" }, }, - 'td': { - tag: 'td', - properties: {'border': '1px solid black'}, + td: { + tag: "td", + properties: { border: "1px solid black" }, }, }; -const DESCRIPTION = 'Replace elements by others with CSS style'; +const DESCRIPTION = "Replace elements by others with CSS style"; function replaceElementsByOtherWithCSS(htmlDoc: HTMLDocument) { for (const fromTag in SIMPLE_TAGS) { @@ -45,20 +45,26 @@ function replaceElementsByOtherWithCSS(htmlDoc: HTMLDocument) { continue; } - const {tag: targetTag, properties} = SIMPLE_TAGS[fromTag]; + const { tag: targetTag, properties } = SIMPLE_TAGS[fromTag]; const elements = Array.from(htmlDoc.querySelectorAll(fromTag)); - elements.forEach(element => replaceElementTag(element, targetTag, properties)); + elements.forEach((element) => + replaceElementTag(element, targetTag, properties), + ); } } -function replaceElementTag(element: Element, targetTag: string, properties: MyCSSProperties) { +function replaceElementTag( + element: Element, + targetTag: string, + properties: MyCSSProperties, +) { const targetElement = createElement(targetTag, properties); replaceElementWithStructure(element, targetElement); } function createElement(tag: string, properties: MyCSSProperties) { const element = document.createElement(tag); - let styleValue = ''; + let styleValue = ""; for (const property in properties) { if (!Object.prototype.hasOwnProperty.call(properties, property)) { continue; @@ -67,7 +73,7 @@ function createElement(tag: string, properties: MyCSSProperties) { styleValue = `${styleValue}${property}: ${properties[property]};`; } if (styleValue.length > 0) { - element.setAttribute('style', styleValue); + element.setAttribute("style", styleValue); } return element; diff --git a/src/replace_elements/replace_simple_elements_tag.ts b/src/replace_elements/replace_simple_elements_tag.ts index 268f75e..1b2859b 100644 --- a/src/replace_elements/replace_simple_elements_tag.ts +++ b/src/replace_elements/replace_simple_elements_tag.ts @@ -1,19 +1,31 @@ -import { Step } from '../step'; -import replaceElementWithStructure from '../replace_element'; +import { Step } from "../step"; +import replaceElementWithStructure from "../replace_element"; const SIMPLE_TAGS: Record = { - 'div': [ - 'article', 'aside', 'details', 'figure', 'fieldset', 'footer', 'form', - 'header', 'hgroup', 'main', 'noframes', 'noscript', 'picture', 'search', - 'section', + div: [ + "article", + "aside", + "details", + "figure", + "fieldset", + "footer", + "form", + "header", + "hgroup", + "main", + "noframes", + "noscript", + "picture", + "search", + "section", ], - 'ul': ['dir'], - 'span': ['bdi', 'data', 'label', 'rt', 'ruby', 'time', 'rp', 'wbr'], - 'p': ['figcaption', 'summary', 'legend'], - 'del': ['s', 'strike'], + ul: ["dir"], + span: ["bdi", "data", "label", "rt", "ruby", "time", "rp", "wbr"], + p: ["figcaption", "summary", "legend"], + del: ["s", "strike"], }; -const DESCRIPTION = 'Replace simple elements tag'; +const DESCRIPTION = "Replace simple elements tag"; function replaceSimpleElementsTag(htmlDoc: HTMLDocument) { for (const targetTag in SIMPLE_TAGS) { @@ -21,9 +33,9 @@ function replaceSimpleElementsTag(htmlDoc: HTMLDocument) { continue; } - const selector = SIMPLE_TAGS[targetTag].join(','); + const selector = SIMPLE_TAGS[targetTag].join(","); const elements = Array.from(htmlDoc.querySelectorAll(selector)); - elements.forEach(element => replaceElementTag(element, targetTag)); + elements.forEach((element) => replaceElementTag(element, targetTag)); } } diff --git a/src/replace_elements/replace_unknown_elements.ts b/src/replace_elements/replace_unknown_elements.ts index 58bf540..143de9f 100644 --- a/src/replace_elements/replace_unknown_elements.ts +++ b/src/replace_elements/replace_unknown_elements.ts @@ -1,44 +1,104 @@ -import { Step } from '../step'; +import { Step } from "../step"; -const DESCRIPTION = 'Replacing unknown HTML elements with div'; +const DESCRIPTION = "Replacing unknown HTML elements with div"; const HTML5_TAGS = new Set([ // Content sectioning - 'address', 'article', 'aside', 'footer', 'header', - 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hgroup', - 'main', 'section', + "address", + "article", + "aside", + "footer", + "header", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "hgroup", + "main", + "section", // Text content - 'blockquote', 'dd', 'div', 'dl', 'dt', 'figcaption', 'figure', - 'hr', 'li', 'ol', 'p', 'pre', 'ul', + "blockquote", + "dd", + "div", + "dl", + "dt", + "figcaption", + "figure", + "hr", + "li", + "ol", + "p", + "pre", + "ul", // Inline text semantics - 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', - 'dfn', 'em', 'i', 'kbd', 'mark', 'q', 'rp', 'rt', 'ruby', - 's', 'samp', 'small', 'span', 'strong', 'sub', 'sup', - 'time', 'u', 'var', 'wbr', + "a", + "abbr", + "b", + "bdi", + "bdo", + "br", + "cite", + "code", + "data", + "dfn", + "em", + "i", + "kbd", + "mark", + "q", + "rp", + "rt", + "ruby", + "s", + "samp", + "small", + "span", + "strong", + "sub", + "sup", + "time", + "u", + "var", + "wbr", // Image and multimedia - 'img', + "img", // Embedded content - 'picture', 'portal', + "picture", + "portal", // Scripting - 'noscript', + "noscript", // Demarcating edits - 'del', 'ins', + "del", + "ins", // Table content - 'caption', 'table', 'tbody', 'td', - 'tfoot', 'th', 'thead', 'tr', + "caption", + "table", + "tbody", + "td", + "tfoot", + "th", + "thead", + "tr", // Forms - 'fieldset', 'form', 'label', - 'legend', 'meter', 'progress', + "fieldset", + "form", + "label", + "legend", + "meter", + "progress", // Interactive elements - 'details', 'summary', + "details", + "summary", ]); function replaceUnknownElements(htmlDoc: HTMLDocument) { - const elements = Array.from(htmlDoc.querySelectorAll('body *')); + const elements = Array.from(htmlDoc.querySelectorAll("body *")); for (const element of elements) { if (!HTML5_TAGS.has(element.tagName.toLowerCase())) { - const div = document.createElement('div'); + const div = document.createElement("div"); while (element.firstChild) { div.appendChild(element.firstChild); } diff --git a/src/split_main_content.ts b/src/split_main_content.ts index 9f592f7..d5fd1e8 100644 --- a/src/split_main_content.ts +++ b/src/split_main_content.ts @@ -1,35 +1,41 @@ -import { Step } from './step'; +import { Step } from "./step"; -const DESCRIPTION = 'Splitting the main content from HTML document by headings'; +const DESCRIPTION = "Splitting the main content from HTML document by headings"; const REMAINING_TEXT_LIMIT = 80; - export interface SplittedElement { title: string; element: Element; } - function splitMainContentByHeadings( mainContent: Element, - metadata: {title: string}, + metadata: { title: string }, ): SplittedElement[] { - const headingsLevel2 = Array.from(mainContent.getElementsByTagName('h2')).filter(hasTitle); + const headingsLevel2 = Array.from( + mainContent.getElementsByTagName("h2"), + ).filter(hasTitle); if (headingsLevel2.length === 0) { - return [{title: metadata.title, element: mainContent}]; + return [{ title: metadata.title, element: mainContent }]; } if (headingsLevel2.length > 1) { return splitMainContent(mainContent, metadata, headingsLevel2); } - const headings = Array.from(mainContent.querySelectorAll('h2, h3')).filter(hasTitle); + const headings = Array.from(mainContent.querySelectorAll("h2, h3")).filter( + hasTitle, + ); return splitMainContent(mainContent, metadata, headings); } -function splitMainContent(mainContent: Element, metadata: {title: string}, elementReferences: Element[]) { +function splitMainContent( + mainContent: Element, + metadata: { title: string }, + elementReferences: Element[], +) { const contentElements = []; for (const elementReference of elementReferences.reverse()) { @@ -42,20 +48,23 @@ function splitMainContent(mainContent: Element, metadata: {title: string}, eleme parentElement = parentElement.parentNode as Element; } - const newRootContentElement = document.createElement('div'); + const newRootContentElement = document.createElement("div"); const title = recursiveSplitContent(ancestors, newRootContentElement); - if (newRootContentElement.textContent.trim() !== '') { - contentElements.push({title, element: newRootContentElement}); + if (newRootContentElement.textContent.trim() !== "") { + contentElements.push({ title, element: newRootContentElement }); } } const remainingText = mainContent.textContent.trim(); if (remainingText.length >= REMAINING_TEXT_LIMIT) { - const newRootContentElement = document.createElement('div'); + const newRootContentElement = document.createElement("div"); moveElementsToNewParent(mainContent, newRootContentElement); - if (newRootContentElement.textContent.trim() !== '') { - contentElements.push({title: metadata.title, element: newRootContentElement}); + if (newRootContentElement.textContent.trim() !== "") { + contentElements.push({ + title: metadata.title, + element: newRootContentElement, + }); } } @@ -66,11 +75,11 @@ function recursiveSplitContent( ancestors: Element[], newParentElement: Element, ): string { - const cloneAncestors = [ ...ancestors ]; + const cloneAncestors = [...ancestors]; const element = cloneAncestors.pop()!; const currentParentElement = element.parentNode as Element; - let title = ''; + let title = ""; if (cloneAncestors.length > 0) { const newElement = cloneElementOnly(element); title = recursiveSplitContent(cloneAncestors, newElement); @@ -97,9 +106,9 @@ function cloneElementOnly(element: Element) { } function copyAttributes(from: Element, to: Element) { - for (const attribute of Array.from(from.attributes)) { - to.setAttribute(attribute.name, attribute.value); - } + for (const attribute of Array.from(from.attributes)) { + to.setAttribute(attribute.name, attribute.value); + } } function moveElementsToNewParentAfter( @@ -133,7 +142,7 @@ function moveElementsToNewParent( } function hasTitle(heading: Element): boolean { - return heading.textContent.trim() !== ''; + return heading.textContent.trim() !== ""; } export default new Step(DESCRIPTION, splitMainContentByHeadings); diff --git a/src/step.ts b/src/step.ts index 4f5a828..38aff63 100644 --- a/src/step.ts +++ b/src/step.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Logger } from './logger'; +import { Logger } from "./logger"; const TEMPLATE_ERROR_MESSAGE = '[ERROR] Error on "%s"'; @@ -33,7 +33,10 @@ export class SubProcessStep extends Step { override run(...args: any[]) { const [callbackStepCompleted, logger, ...params] = args; - return this.processFactory(...params).process(callbackStepCompleted, logger); + return this.processFactory(...params).process( + callbackStepCompleted, + logger, + ); } getStepCount() { @@ -42,11 +45,11 @@ export class SubProcessStep extends Step { } export class Process { - private stepsFlow: {step: Step, dependenciesIndex: number[]}[]; + private stepsFlow: { step: Step; dependenciesIndex: number[] }[]; - constructor(steps: {step: Step, dependencies?: Step[]}[]) { + constructor(steps: { step: Step; dependencies?: Step[] }[]) { this.stepsFlow = []; - steps.forEach(step => this.addStep(step.step, step.dependencies)); + steps.forEach((step) => this.addStep(step.step, step.dependencies)); } private addStep(step: Step, dependencies: Step[] = []) { @@ -64,8 +67,12 @@ export class Process { } if (!found) { - const dependenciesNames = dependencies.map(dependency => dependency.name); - throw new Error(`Failed to create execution dependency. Step "${step.name}" depends on: ${dependenciesNames}.`); + const dependenciesNames = dependencies.map( + (dependency) => dependency.name, + ); + throw new Error( + `Failed to create execution dependency. Step "${step.name}" depends on: ${dependenciesNames}.`, + ); } } @@ -73,7 +80,10 @@ export class Process { } getLength(): number { - return this.stepsFlow.reduce((sum, { step }) => sum + step.getStepCount(), 0); + return this.stepsFlow.reduce( + (sum, { step }) => sum + step.getStepCount(), + 0, + ); } async process(callbackStepCompleted: () => void, logger: Logger) { @@ -81,7 +91,7 @@ export class Process { let result: any = null; for (const { step, dependenciesIndex } of this.stepsFlow) { - const params: any[] = dependenciesIndex.map(idx => results[idx]); + const params: any[] = dependenciesIndex.map((idx) => results[idx]); logger.log(step.name); @@ -98,7 +108,7 @@ export class Process { results.push(result); } } catch (error) { - const message = TEMPLATE_ERROR_MESSAGE.replace('%s', step.name); + const message = TEMPLATE_ERROR_MESSAGE.replace("%s", step.name); logger.error(message); throw error; } diff --git a/test/clean_document/remove_attributes.test.ts b/test/clean_document/remove_attributes.test.ts index 88cb474..9fd263e 100644 --- a/test/clean_document/remove_attributes.test.ts +++ b/test/clean_document/remove_attributes.test.ts @@ -1,87 +1,89 @@ -import { test } from 'node:test'; -import * as assert from 'node:assert/strict'; -import '../../test/setup'; -import removeAttributes from '../../src/clean_document/remove_attributes'; +import { test } from "node:test"; +import * as assert from "node:assert/strict"; +import "../../test/setup"; +import removeAttributes from "../../src/clean_document/remove_attributes"; -test('removes the style attribute', () => { +test("removes the style attribute", () => { const doc = makeDoc('

text

'); removeAttributes.run(doc); - assert.equal(doc.querySelector('p')!.hasAttribute('style'), false); + assert.equal(doc.querySelector("p")!.hasAttribute("style"), false); }); -test('removes the class attribute', () => { +test("removes the class attribute", () => { const doc = makeDoc('

text

'); removeAttributes.run(doc); - assert.equal(doc.querySelector('p')!.hasAttribute('class'), false); + assert.equal(doc.querySelector("p")!.hasAttribute("class"), false); }); -test('removes mouse event handler attributes', () => { - const doc = makeDoc(''); +test("removes mouse event handler attributes", () => { + const doc = makeDoc( + '', + ); removeAttributes.run(doc); - const button = doc.querySelector('button')!; + const button = doc.querySelector("button")!; - assert.equal(button.hasAttribute('onclick'), false); - assert.equal(button.hasAttribute('onmouseover'), false); + assert.equal(button.hasAttribute("onclick"), false); + assert.equal(button.hasAttribute("onmouseover"), false); }); -test('removes keyboard event handler attributes', () => { +test("removes keyboard event handler attributes", () => { const doc = makeDoc(''); removeAttributes.run(doc); - const input = doc.querySelector('input')!; + const input = doc.querySelector("input")!; - assert.equal(input.hasAttribute('onkeydown'), false); - assert.equal(input.hasAttribute('onkeyup'), false); + assert.equal(input.hasAttribute("onkeydown"), false); + assert.equal(input.hasAttribute("onkeyup"), false); }); -test('removes the srcset attribute', () => { +test("removes the srcset attribute", () => { const doc = makeDoc(''); removeAttributes.run(doc); - assert.equal(doc.querySelector('img')!.hasAttribute('srcset'), false); + assert.equal(doc.querySelector("img")!.hasAttribute("srcset"), false); }); -test('removes the tabindex attribute', () => { +test("removes the tabindex attribute", () => { const doc = makeDoc('
focus me
'); removeAttributes.run(doc); - assert.equal(doc.querySelector('div')!.hasAttribute('tabindex'), false); + assert.equal(doc.querySelector("div")!.hasAttribute("tabindex"), false); }); -test('preserves href on anchor elements', () => { +test("preserves href on anchor elements", () => { const doc = makeDoc('link'); removeAttributes.run(doc); - const a = doc.querySelector('a')!; + const a = doc.querySelector("a")!; - assert.equal(a.getAttribute('href'), 'https://example.com'); - assert.equal(a.hasAttribute('class'), false); + assert.equal(a.getAttribute("href"), "https://example.com"); + assert.equal(a.hasAttribute("class"), false); }); -test('preserves src and alt on image elements', () => { +test("preserves src and alt on image elements", () => { const doc = makeDoc('A photo'); removeAttributes.run(doc); - const img = doc.querySelector('img')!; + const img = doc.querySelector("img")!; - assert.equal(img.getAttribute('src'), 'photo.jpg'); - assert.equal(img.getAttribute('alt'), 'A photo'); - assert.equal(img.hasAttribute('class'), false); + assert.equal(img.getAttribute("src"), "photo.jpg"); + assert.equal(img.getAttribute("alt"), "A photo"); + assert.equal(img.hasAttribute("class"), false); }); function makeDoc(bodyHTML: string): HTMLDocument { - const doc = document.implementation.createHTMLDocument(''); + const doc = document.implementation.createHTMLDocument(""); doc.body.innerHTML = bodyHTML; return doc as HTMLDocument; } diff --git a/test/clean_document/remove_comments.test.ts b/test/clean_document/remove_comments.test.ts index 885a590..2715931 100644 --- a/test/clean_document/remove_comments.test.ts +++ b/test/clean_document/remove_comments.test.ts @@ -1,56 +1,59 @@ -import { test } from 'node:test'; -import * as assert from 'node:assert/strict'; -import '../../test/setup'; -import removeComments from '../../src/clean_document/remove_comments'; +import { test } from "node:test"; +import * as assert from "node:assert/strict"; +import "../../test/setup"; +import removeComments from "../../src/clean_document/remove_comments"; -test('removes a comment inside an element', () => { - const doc = makeDoc('

textmore

'); +test("removes a comment inside an element", () => { + const doc = makeDoc("

textmore

"); removeComments.run(doc); const comments = collectComments(doc); assert.equal(comments.length, 0); - assert.equal(doc.querySelector('p')!.textContent, 'textmore'); + assert.equal(doc.querySelector("p")!.textContent, "textmore"); }); -test('removes multiple comments throughout the document', () => { - const doc = makeDoc('

text

'); +test("removes multiple comments throughout the document", () => { + const doc = makeDoc("

text

"); removeComments.run(doc); const comments = collectComments(doc); assert.equal(comments.length, 0); - assert.equal(doc.querySelector('p')!.textContent, 'text'); + assert.equal(doc.querySelector("p")!.textContent, "text"); }); -test('preserves element nodes when removing comments', () => { - const doc = makeDoc('

keep

'); +test("preserves element nodes when removing comments", () => { + const doc = makeDoc("

keep

"); removeComments.run(doc); - assert.equal(doc.querySelectorAll('p').length, 1); - assert.equal(doc.querySelector('p')!.textContent, 'keep'); + assert.equal(doc.querySelectorAll("p").length, 1); + assert.equal(doc.querySelector("p")!.textContent, "keep"); }); -test('does nothing when there are no comments', () => { - const doc = makeDoc('

no comments here

'); +test("does nothing when there are no comments", () => { + const doc = makeDoc("

no comments here

"); removeComments.run(doc); - assert.equal(doc.querySelector('p')!.textContent, 'no comments here'); + assert.equal(doc.querySelector("p")!.textContent, "no comments here"); }); function makeDoc(bodyHTML: string): HTMLDocument { - const doc = document.implementation.createHTMLDocument(''); + const doc = document.implementation.createHTMLDocument(""); doc.body.innerHTML = bodyHTML; return doc as HTMLDocument; } function collectComments(doc: HTMLDocument): Comment[] { const comments: Comment[] = []; - const iterator = doc.createNodeIterator(doc.documentElement, NodeFilter.SHOW_COMMENT); + const iterator = doc.createNodeIterator( + doc.documentElement, + NodeFilter.SHOW_COMMENT, + ); let node: Comment; while ((node = iterator.nextNode() as Comment) !== null) { comments.push(node); diff --git a/test/clean_document/remove_data_attributes.test.ts b/test/clean_document/remove_data_attributes.test.ts index 67a011f..8f7d896 100644 --- a/test/clean_document/remove_data_attributes.test.ts +++ b/test/clean_document/remove_data_attributes.test.ts @@ -1,60 +1,62 @@ -import { test } from 'node:test'; -import * as assert from 'node:assert/strict'; -import '../../test/setup'; -import removeDataAttributes from '../../src/clean_document/remove_data_attributes'; +import { test } from "node:test"; +import * as assert from "node:assert/strict"; +import "../../test/setup"; +import removeDataAttributes from "../../src/clean_document/remove_data_attributes"; -test('removes a data- attribute from an element', () => { +test("removes a data- attribute from an element", () => { const doc = makeDoc('

text

'); removeDataAttributes.run(doc); - assert.equal(doc.querySelector('p')!.hasAttribute('data-id'), false); + assert.equal(doc.querySelector("p")!.hasAttribute("data-id"), false); }); -test('removes multiple data- attributes from the same element', () => { +test("removes multiple data- attributes from the same element", () => { const doc = makeDoc('

text

'); removeDataAttributes.run(doc); - const p = doc.querySelector('p')!; + const p = doc.querySelector("p")!; - assert.equal(p.hasAttribute('data-id'), false); - assert.equal(p.hasAttribute('data-value'), false); + assert.equal(p.hasAttribute("data-id"), false); + assert.equal(p.hasAttribute("data-value"), false); }); -test('removes data- attributes from nested elements', () => { - const doc = makeDoc('
text
'); +test("removes data- attributes from nested elements", () => { + const doc = makeDoc( + '
text
', + ); removeDataAttributes.run(doc); - assert.equal(doc.querySelector('div')!.hasAttribute('data-outer'), false); - assert.equal(doc.querySelector('span')!.hasAttribute('data-inner'), false); + assert.equal(doc.querySelector("div")!.hasAttribute("data-outer"), false); + assert.equal(doc.querySelector("span")!.hasAttribute("data-inner"), false); }); -test('preserves non-data- attributes', () => { +test("preserves non-data- attributes", () => { const doc = makeDoc('link'); removeDataAttributes.run(doc); - const a = doc.querySelector('a')!; + const a = doc.querySelector("a")!; - assert.equal(a.hasAttribute('data-track'), false); - assert.equal(a.getAttribute('href'), 'https://example.com'); + assert.equal(a.hasAttribute("data-track"), false); + assert.equal(a.getAttribute("href"), "https://example.com"); }); -test('does nothing when there are no data- attributes', () => { +test("does nothing when there are no data- attributes", () => { const doc = makeDoc('

content

'); removeDataAttributes.run(doc); - const p = doc.querySelector('p')!; + const p = doc.querySelector("p")!; - assert.equal(p.getAttribute('id'), 'main'); - assert.equal(p.getAttribute('class'), 'text'); + assert.equal(p.getAttribute("id"), "main"); + assert.equal(p.getAttribute("class"), "text"); }); function makeDoc(bodyHTML: string): HTMLDocument { - const doc = document.implementation.createHTMLDocument(''); + const doc = document.implementation.createHTMLDocument(""); doc.body.innerHTML = bodyHTML; return doc as HTMLDocument; } diff --git a/test/clean_document/remove_elements.test.ts b/test/clean_document/remove_elements.test.ts index 13d50a8..8d12d24 100644 --- a/test/clean_document/remove_elements.test.ts +++ b/test/clean_document/remove_elements.test.ts @@ -1,96 +1,100 @@ -import { test } from 'node:test'; -import * as assert from 'node:assert/strict'; -import '../../test/setup'; -import removeElements from '../../src/clean_document/remove_elements'; +import { test } from "node:test"; +import * as assert from "node:assert/strict"; +import "../../test/setup"; +import removeElements from "../../src/clean_document/remove_elements"; -test('removes '); +test("removes "); removeElements.run(doc); - assert.equal(doc.querySelectorAll('script').length, 0); - assert.equal(doc.querySelectorAll('p').length, 1); + assert.equal(doc.querySelectorAll("script").length, 0); + assert.equal(doc.querySelectorAll("p").length, 1); }); -test('removes

text

'); +test("removes

text

"); removeElements.run(doc); - assert.equal(doc.querySelectorAll('style').length, 0); + assert.equal(doc.querySelectorAll("style").length, 0); }); -test('removes '); removeElements.run(doc); - assert.equal(doc.querySelectorAll('iframe').length, 0); + assert.equal(doc.querySelectorAll("iframe").length, 0); }); -test('removes form elements', () => { - const doc = makeDoc('
'); +test("removes form elements", () => { + const doc = makeDoc( + '
', + ); removeElements.run(doc); - assert.equal(doc.querySelectorAll('input').length, 0); - assert.equal(doc.querySelectorAll('button').length, 0); - assert.equal(doc.querySelectorAll('select').length, 0); + assert.equal(doc.querySelectorAll("input").length, 0); + assert.equal(doc.querySelectorAll("button").length, 0); + assert.equal(doc.querySelectorAll("select").length, 0); }); -test('removes