diff --git a/.changeset/auto-transform-bible-html.md b/.changeset/auto-transform-bible-html.md
new file mode 100644
index 00000000..f906d672
--- /dev/null
+++ b/.changeset/auto-transform-bible-html.md
@@ -0,0 +1,7 @@
+---
+"@youversion/platform-core": minor
+"@youversion/platform-react-hooks": minor
+"@youversion/platform-react-ui": minor
+---
+
+Auto-transform Bible HTML in `getPassage` — verse wrapping, footnote extraction, sanitization, and table fixes now happen automatically. Consumers no longer need to call `transformBibleHtml` manually. Uses native DOMParser in browser, dynamic `import('jsdom')` on server. `jsdom` is now declared as an optional peer dependency so install logs surface it for server consumers. Added `data-yv-transformed` idempotency marker so double-transforms are a no-op. Pass `transform: false` to receive raw, untransformed HTML (useful for simple display or when `jsdom` is unavailable). Bible reader CSS now handles verse label spacing for untransformed HTML automatically.
diff --git a/packages/core/AGENTS.md b/packages/core/AGENTS.md
index 0278a742..493cc042 100644
--- a/packages/core/AGENTS.md
+++ b/packages/core/AGENTS.md
@@ -23,7 +23,7 @@ YouVersionAPI.ts # Base YouVersion API client
SignInWithYouVersionPKCE.ts # PKCE auth implementation
StorageStrategy.ts # Storage interface (SessionStorage, MemoryStorage)
bible-html-transformer.ts # Runtime-agnostic transformer (also contains browser convenience fn)
-bible-html-transformer-server.ts # Server convenience wrapper (uses linkedom)
+bible-html-transformer-server.ts # Server convenience wrapper (uses jsdom)
browser.ts # Browser entry point
server.ts # Server entry point
index.ts # Main entry point (runtime-agnostic)
@@ -64,7 +64,7 @@ The Bible HTML transformer provides both a runtime-agnostic core and environment
- `@youversion/platform-core` → Runtime-agnostic `transformBibleHtml` (requires DOM adapters)
- `@youversion/platform-core/browser` → Browser convenience wrapper (uses native DOMParser)
-- `@youversion/platform-core/server` → Server convenience wrapper (uses linkedom)
+- `@youversion/platform-core/server` → Server convenience wrapper (uses jsdom)
**Examples:**
@@ -82,7 +82,7 @@ import { transformBibleHtml } from '@youversion/platform-core/browser';
const result = transformBibleHtml(html);
-// Server convenience (uses linkedom, requires: npm install linkedom)
+// Server convenience (uses jsdom, requires: npm install jsdom)
import { transformBibleHtml } from '@youversion/platform-core/server';
const result = transformBibleHtml(html);
@@ -90,7 +90,7 @@ const result = transformBibleHtml(html);
**Why separate entry points?**
-This architecture keeps the main export truly runtime-agnostic while providing ergonomic convenience wrappers for common environments. The separate `/browser` and `/server` entry points ensure optimal bundle sizes - linkedom won't be bundled in browser builds.
+This architecture keeps the main export truly runtime-agnostic while providing ergonomic convenience wrappers for common environments. The separate `/browser` and `/server` entry points ensure optimal bundle sizes - jsdom won't be bundled in browser builds.
## ADDING A NEW ENDPOINT OR CLIENT
diff --git a/packages/core/package.json b/packages/core/package.json
index e13428cd..ec383f30 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -47,6 +47,7 @@
"devDependencies": {
"@internal/eslint-config": "workspace:*",
"@internal/tsconfig": "workspace:*",
+ "@types/jsdom": "^28.0.1",
"@vitest/coverage-v8": "4.0.4",
"dotenv-cli": "7.4.2",
"eslint": "9.38.0",
@@ -57,10 +58,10 @@
"vitest": "4.0.4"
},
"peerDependencies": {
- "linkedom": "^0.18.12"
+ "jsdom": "^24.0.0"
},
"peerDependenciesMeta": {
- "linkedom": {
+ "jsdom": {
"optional": true
}
},
diff --git a/packages/core/src/__tests__/bible.test.ts b/packages/core/src/__tests__/bible.test.ts
index 7f3b961f..38834f0f 100644
--- a/packages/core/src/__tests__/bible.test.ts
+++ b/packages/core/src/__tests__/bible.test.ts
@@ -507,18 +507,16 @@ describe('BibleClient', () => {
});
describe('getPassage', () => {
- it('should fetch a passage for a verse', async () => {
+ it('should fetch a passage for a verse and auto-transform HTML', async () => {
const passage = await bibleClient.getPassage(111, 'GEN.1.1');
const { success } = BiblePassageSchema.safeParse(passage);
expect(success).toBe(true);
- expect(passage).toEqual({
- id: 'GEN.1.1',
- content:
- '
1In the beginning God created the heavens and the earth.
',
- reference: 'Genesis 1:1',
- });
+ expect(passage.id).toBe('GEN.1.1');
+ expect(passage.reference).toBe('Genesis 1:1');
+ expect(passage.content).toContain('data-yv-transformed');
+ expect(passage.content).toContain('In the beginning God created');
});
it('should fetch a passage for a chapter', async () => {
@@ -534,13 +532,29 @@ describe('BibleClient', () => {
it('should fetch a passage with html format by default', async () => {
const passage = await bibleClient.getPassage(111, 'GEN.1.1');
- expect(passage.content).toContain('');
+ expect(passage.content).toContain('
{
+ it('should not transform text format', async () => {
const passage = await bibleClient.getPassage(111, 'GEN.1.1', 'text');
expect(passage.content).not.toContain('
');
+ expect(passage.content).not.toContain('data-yv-transformed');
+ });
+
+ it('should skip transformation when transform is false', async () => {
+ const passage = await bibleClient.getPassage(
+ 111,
+ 'GEN.1.1',
+ 'html',
+ undefined,
+ undefined,
+ false,
+ );
+
+ expect(passage.content).toContain('
{
@@ -548,14 +562,15 @@ describe('BibleClient', () => {
expect(passage.id).toBe('ROM.1');
expect(passage.content).toContain('yv-h');
- expect(passage.content).not.toContain('yv-n');
+ expect(passage.content).not.toContain('data-verse-footnote');
});
- it('should fetch a passage with include_notes', async () => {
+ it('should fetch a passage with include_notes and transform footnotes', async () => {
const passage = await bibleClient.getPassage(111, 'ROM.1', 'html', undefined, true);
expect(passage.id).toBe('ROM.1');
- expect(passage.content).toContain('yv-n');
+ // Footnotes are transformed into data-verse-footnote anchors
+ expect(passage.content).toContain('data-verse-footnote');
expect(passage.content).not.toContain('yv-h');
});
@@ -563,7 +578,7 @@ describe('BibleClient', () => {
const passage = await bibleClient.getPassage(111, 'ROM.1', 'html', true, true);
expect(passage.id).toBe('ROM.1');
- expect(passage.content).toContain('yv-n');
+ expect(passage.content).toContain('data-verse-footnote');
expect(passage.content).toContain('yv-h');
});
diff --git a/packages/core/src/bible-html-transformer-server.ts b/packages/core/src/bible-html-transformer-server.ts
index e7e25911..6b42819c 100644
--- a/packages/core/src/bible-html-transformer-server.ts
+++ b/packages/core/src/bible-html-transformer-server.ts
@@ -1,4 +1,4 @@
-import { DOMParser } from 'linkedom';
+import { JSDOM } from 'jsdom';
import {
transformBibleHtml as transformBibleHtmlWithAdapters,
@@ -6,14 +6,11 @@ import {
} from './bible-html-transformer';
/**
- * Transforms Bible HTML for server environments using linkedom.
+ * Transforms Bible HTML for server environments using jsdom.
*
- * Import from `@youversion/platform-core/server` to avoid bundling linkedom
+ * Import from `@youversion/platform-core/server` to avoid bundling jsdom
* in client-side builds.
*
- * linkedom requires HTML to be wrapped in body tags for `doc.body.innerHTML`
- * to work correctly, so this function handles that wrapping automatically.
- *
* @param html - The raw Bible HTML from the YouVersion API
* @returns The transformed HTML
*
@@ -28,10 +25,7 @@ import {
export function transformBibleHtml(html: string): TransformedBibleHtml {
return transformBibleHtmlWithAdapters(html, {
parseHtml: (h: string) =>
- new DOMParser().parseFromString(
- `${h}`,
- 'text/html',
- ) as unknown as Document,
+ new JSDOM(`${h}`).window.document,
serializeHtml: (doc: Document) => doc.body.innerHTML,
});
}
diff --git a/packages/core/src/bible-html-transformer.server.test.ts b/packages/core/src/bible-html-transformer.server.test.ts
index 3d4dc69a..1df9f0b6 100644
--- a/packages/core/src/bible-html-transformer.server.test.ts
+++ b/packages/core/src/bible-html-transformer.server.test.ts
@@ -5,7 +5,7 @@ import { describe, it, expect } from 'vitest';
import { transformBibleHtml } from './bible-html-transformer-server';
describe('transformBibleHtml', () => {
- it('should transform HTML using linkedom', () => {
+ it('should transform HTML using jsdom', () => {
const html = `
@@ -58,7 +58,7 @@ describe('transformBibleHtml', () => {
const result = transformBibleHtml(html);
- // linkedom may serialize attributes in different order than browsers
+ // jsdom may serialize attributes in different order than browsers
expect(result.html).toContain('class="yv-v"');
expect(result.html).toContain('v="1"');
expect(result.html).toContain('v="2"');
@@ -77,8 +77,8 @@ describe('transformBibleHtml', () => {
const result = transformBibleHtml(html);
- // linkedom encodes non-breaking space as instead of the raw character
- expect(result.html).toMatch(/1(\u00A0| )/);
+ // jsdom may encode non-breaking space as instead of the raw character
+ expect(result.html).toMatch(/1(\u00A0| | )/);
});
it('should handle intro chapter footnotes', () => {
@@ -137,7 +137,7 @@ describe('transformBibleHtml', () => {
expect(result.html).toContain('Click me');
});
- it('should preserve safe Bible HTML through linkedom', () => {
+ it('should preserve safe Bible HTML through jsdom', () => {
const html = `
Jesus said
diff --git a/packages/core/src/bible-html-transformer.test.ts b/packages/core/src/bible-html-transformer.test.ts
index fee794b1..8f1cd97b 100644
--- a/packages/core/src/bible-html-transformer.test.ts
+++ b/packages/core/src/bible-html-transformer.test.ts
@@ -308,7 +308,7 @@ describe('transformBibleHtml - sanitization', () => {
const result = transformBibleHtml(html, createAdapters());
expect(result.html).not.toContain('onclick');
- expect(result.html).toContain('
');
+ expect(result.html).toContain('
{
const result = transformBibleHtml(html, createAdapters());
expect(result.html).not.toContain('style');
- expect(result.html).toContain('
');
+ expect(result.html).toContain('
{
});
});
+describe('transformBibleHtml - idempotency', () => {
+ it('should add data-yv-transformed marker after transforming', () => {
+ const html =
+ '
';
+ const result = transformBibleHtml(html, createAdapters());
+
+ expect(result.html).toContain('data-yv-transformed');
+ });
+
+ it('should short-circuit when HTML is already transformed', () => {
+ const html =
+ '
';
+ const first = transformBibleHtml(html, createAdapters());
+ const second = transformBibleHtml(first.html, createAdapters());
+
+ expect(second.html).toBe(first.html);
+ });
+
+ it('should produce identical output when transformed twice (idempotent)', () => {
+ const html =
+ '
';
+ const first = transformBibleHtml(html, createAdapters());
+ const second = transformBibleHtml(first.html, createAdapters());
+
+ expect(second.html).toBe(first.html);
+ });
+});
+
describe('transformBibleHtmlForBrowser - DOMParser fallback', () => {
it('should throw when DOMParser is unavailable', () => {
const original = globalThis.DOMParser;
diff --git a/packages/core/src/bible-html-transformer.ts b/packages/core/src/bible-html-transformer.ts
index 448a26cc..be1d4c83 100644
--- a/packages/core/src/bible-html-transformer.ts
+++ b/packages/core/src/bible-html-transformer.ts
@@ -2,6 +2,8 @@ const NON_BREAKING_SPACE = '\u00A0';
const FOOTNOTE_KEY_ATTR = 'data-footnote-key';
+const TRANSFORMED_ATTR = 'data-yv-transformed';
+
const NEEDS_SPACE_BEFORE = /^[^\s.,;:!?)}\]'"'»›]/;
const ALLOWED_TAGS = new Set([
@@ -339,6 +341,11 @@ export function transformBibleHtml(
const doc = options.parseHtml(html);
sanitizeBibleHtmlDocument(doc);
+
+ // Already transformed — skip structural transforms
+ if (doc.querySelector(`[${TRANSFORMED_ATTR}]`)) {
+ return { html: options.serializeHtml(doc) };
+ }
wrapVerseContent(doc);
assignFootnoteKeys(doc);
@@ -348,6 +355,10 @@ export function transformBibleHtml(
addNbspToVerseLabels(doc);
fixIrregularTables(doc);
+ // Mark as transformed for idempotency
+ const root = doc.body?.firstElementChild ?? doc.body;
+ root?.setAttribute(TRANSFORMED_ATTR, '');
+
const transformedHtml = options.serializeHtml(doc);
return { html: transformedHtml };
}
diff --git a/packages/core/src/bible.ts b/packages/core/src/bible.ts
index 918fdf3a..5fa128c1 100644
--- a/packages/core/src/bible.ts
+++ b/packages/core/src/bible.ts
@@ -1,5 +1,6 @@
import { z } from 'zod';
import type { ApiClient } from './client';
+import { transformBibleHtml, type TransformBibleHtmlOptions } from './bible-html-transformer';
import { BibleVersionSchema } from './schemas';
import type {
BibleBook,
@@ -13,6 +14,31 @@ import type {
VOTD,
} from './types';
+async function getHtmlAdapters(): Promise
{
+ if (typeof globalThis.DOMParser !== 'undefined') {
+ return {
+ parseHtml: (h) =>
+ new globalThis.DOMParser().parseFromString(h, 'text/html') as unknown as Document,
+ serializeHtml: (doc) => doc.body.innerHTML,
+ };
+ }
+ let jsdom;
+ try {
+ jsdom = await import('jsdom');
+ } catch {
+ throw new Error(
+ 'Server-side HTML transformation requires "jsdom". ' +
+ 'Install it as a dependency or pass transform: false to skip transformation.',
+ );
+ }
+ return {
+ parseHtml: (h) =>
+ new jsdom.JSDOM(`${h}`).window
+ .document as unknown as Document,
+ serializeHtml: (doc) => doc.body.innerHTML,
+ };
+}
+
/**
* Client for interacting with Bible API endpoints.
*/
@@ -234,18 +260,23 @@ export class BibleClient {
/**
* Fetches a passage (range of verses) from the Bible using the passages endpoint.
- * This is the new API format that returns HTML-formatted content.
*
- * Note: The HTML returned from the API contains inline footnote content that should
- * be transformed before rendering. Use `transformBibleHtml()` or
- * `transformBibleHtmlForBrowser()` to clean up the HTML and extract footnotes.
+ * When format is "html" (the default), the returned content is automatically
+ * sanitized and transformed — verse content is wrapped for CSS targeting,
+ * footnotes are extracted into data attributes, and verse labels get
+ * non-breaking spaces. No manual call to `transformBibleHtml` is needed.
*
* @param versionId The version ID.
* @param usfm The USFM reference (e.g., "JHN.3.1-2", "GEN.1", "JHN.3.16").
* @param format The format to return ("html" or "text", default: "html").
* @param include_headings Whether to include headings in the content.
* @param include_notes Whether to include notes in the content.
- * @returns The requested BiblePassage object with HTML content.
+ * @param transform Whether to auto-transform HTML content (default: `true`).
+ * Set to `false` to receive the original, untransformed HTML from the API.
+ * Raw HTML is sufficient for simple display (e.g., verse-of-the-day) where
+ * verse-level interactivity like highlighting or footnote popovers isn't
+ * needed. Also avoids the `jsdom` dependency on the server.
+ * @returns The requested BiblePassage object.
*
* @example
* ```ts
@@ -258,9 +289,11 @@ export class BibleClient {
* // Get an entire chapter
* const chapter = await bibleClient.getPassage(3034, "GEN.1");
*
- * // Transform HTML before rendering
- * const passage = await bibleClient.getPassage(3034, "JHN.3.16", "html", true, true);
- * const transformed = transformBibleHtmlForBrowser(passage.content);
+ * // Get plain text (no transformation applied)
+ * const text = await bibleClient.getPassage(3034, "JHN.3.16", "text");
+ *
+ * // Get raw, untransformed HTML (no jsdom needed on server)
+ * const raw = await bibleClient.getPassage(3034, "JHN.3.16", "html", undefined, undefined, false);
* ```
*/
async getPassage(
@@ -269,6 +302,7 @@ export class BibleClient {
format: 'html' | 'text' = 'html',
include_headings?: boolean,
include_notes?: boolean,
+ transform?: boolean,
): Promise {
BibleClient.versionIdSchema.parse(versionId);
if (include_headings !== undefined) {
@@ -286,7 +320,18 @@ export class BibleClient {
if (include_notes !== undefined) {
params.include_notes = include_notes;
}
- return this.client.get(`/v1/bibles/${versionId}/passages/${usfm}`, params);
+ const passage = await this.client.get(
+ `/v1/bibles/${versionId}/passages/${usfm}`,
+ params,
+ );
+
+ if (format === 'html' && transform !== false) {
+ const adapters = await getHtmlAdapters();
+ const { html } = transformBibleHtml(passage.content, adapters);
+ return { ...passage, content: html };
+ }
+
+ return passage;
}
/**
diff --git a/packages/core/src/styles/bible-reader.css b/packages/core/src/styles/bible-reader.css
index 870a662d..54459916 100644
--- a/packages/core/src/styles/bible-reader.css
+++ b/packages/core/src/styles/bible-reader.css
@@ -90,6 +90,12 @@
font-family: var(--yv-font-sans);
}
+ /* When content hasn't been JS-transformed, add spacing after verse labels via CSS.
+ Transformed HTML already has a \u00A0 inserted by addNbspToVerseLabels(). */
+ &:not(:has([data-yv-transformed])) .yv-vlbl::after {
+ content: "\00A0";
+ }
+
/* \f - Footnote container (YouVersion wrapper) */
& .yv-n {
display: none;
diff --git a/packages/ui/src/components/verse.tsx b/packages/ui/src/components/verse.tsx
index bec78627..2dd739d4 100644
--- a/packages/ui/src/components/verse.tsx
+++ b/packages/ui/src/components/verse.tsx
@@ -407,9 +407,8 @@ export const Verse = {
}: VerseHtmlProps,
ref,
): ReactNode => {
- // transformBibleHtml uses the browser's native DOMParser, which doesn't
- // exist during SSR. Return raw html on the server; the client-side
- // useLayoutEffect in BibleTextHtml will handle it after hydration.
+ // SSR safety: DOMParser doesn't exist during server render.
+ // Idempotent — already-transformed HTML from getPassage is a no-op.
const transformedHtml = useMemo(
() => (typeof window === 'undefined' ? html : transformBibleHtml(html).html),
[html],
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2cb53637..d5691dc7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -136,9 +136,6 @@ importers:
packages/core:
dependencies:
- linkedom:
- specifier: ^0.18.12
- version: 0.18.12
zod:
specifier: 4.1.12
version: 4.1.12
@@ -149,6 +146,9 @@ importers:
'@internal/tsconfig':
specifier: workspace:*
version: link:../../tools/tsconfig
+ '@types/jsdom':
+ specifier: ^28.0.1
+ version: 28.0.1
'@vitest/coverage-v8':
specifier: 4.0.4
version: 4.0.4(@vitest/browser@4.0.4(msw@2.11.6(@types/node@24.11.0)(typescript@5.9.3))(vite@7.1.11(@types/node@24.11.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(yaml@2.8.1))(vitest@4.0.4(@types/node@24.11.0)(@vitest/browser-playwright@4.0.4)(jiti@2.6.1)(jsdom@24.0.0)(lightningcss@1.31.1)(msw@2.11.6(@types/node@24.11.0)(typescript@5.9.3))(terser@5.44.0)(yaml@2.8.1)))(vitest@4.0.4(@types/node@24.11.0)(@vitest/browser-playwright@4.0.4)(jiti@2.6.1)(jsdom@24.0.0)(lightningcss@1.31.1)(msw@2.11.6(@types/node@24.11.0)(typescript@5.9.3))(terser@5.44.0)(yaml@2.8.1))
@@ -2874,6 +2874,9 @@ packages:
'@types/istanbul-lib-coverage@2.0.6':
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
+ '@types/jsdom@28.0.1':
+ resolution: {integrity: sha512-GJq2QE4TAZ5ajSoCasn5DOFm8u1mI3tIFvM5tIq3W5U/RTB6gsHwc6Yhpl91X9VSDOUVblgXmG+2+sSvFQrdlw==}
+
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -2906,6 +2909,9 @@ packages:
'@types/statuses@2.0.6':
resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==}
+ '@types/tough-cookie@4.0.5':
+ resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
+
'@types/validate-npm-package-name@4.0.2':
resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==}
@@ -3295,9 +3301,6 @@ packages:
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
engines: {node: '>=18'}
- boolbase@1.0.0:
- resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
-
brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
@@ -3521,17 +3524,10 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
- css-select@5.2.2:
- resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
-
css-tree@3.1.0:
resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
- css-what@6.2.2:
- resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
- engines: {node: '>= 6'}
-
css.escape@1.5.1:
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
@@ -3540,9 +3536,6 @@ packages:
engines: {node: '>=4'}
hasBin: true
- cssom@0.5.0:
- resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==}
-
cssstyle@4.6.0:
resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
engines: {node: '>=18'}
@@ -3688,19 +3681,6 @@ packages:
dom-accessibility-api@0.6.3:
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
- dom-serializer@2.0.0:
- resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
-
- domelementtype@2.3.0:
- resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
-
- domhandler@5.0.3:
- resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
- engines: {node: '>= 4'}
-
- domutils@3.2.2:
- resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
-
dotenv-cli@7.4.2:
resolution: {integrity: sha512-SbUj8l61zIbzyhIbg0FwPJq6+wjbzdn9oEtozQpZ6kW2ihCcapKVZj49oCT3oPM+mgQm+itgvUQcG5szxVrZTA==}
hasBin: true
@@ -3770,18 +3750,10 @@ packages:
resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==}
engines: {node: '>=8.6'}
- entities@4.5.0:
- resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
- engines: {node: '>=0.12'}
-
entities@6.0.1:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
- entities@7.0.1:
- resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
- engines: {node: '>=0.12'}
-
env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
@@ -4312,15 +4284,9 @@ packages:
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
- html-escaper@3.0.3:
- resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
-
html-parse-stringify@3.0.1:
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
- htmlparser2@10.1.0:
- resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
-
http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
@@ -4890,15 +4856,6 @@ packages:
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
- linkedom@0.18.12:
- resolution: {integrity: sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q==}
- engines: {node: '>=16'}
- peerDependencies:
- canvas: '>= 2'
- peerDependenciesMeta:
- canvas:
- optional: true
-
lint-staged@16.2.5:
resolution: {integrity: sha512-o36wH3OX0jRWqDw5dOa8a8x6GXTKaLM+LvhRaucZxez0IxA+KNDUCiyjBfNgsMNmchwSX6urLSL7wShcUqAang==}
engines: {node: '>=20.17'}
@@ -5184,9 +5141,6 @@ packages:
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
engines: {node: '>=18'}
- nth-check@2.1.1:
- resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
-
nwsapi@2.2.22:
resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==}
@@ -6274,9 +6228,6 @@ packages:
ufo@1.6.1:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
- uhyphen@0.2.0:
- resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==}
-
unbox-primitive@1.1.0:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
@@ -6284,6 +6235,9 @@ packages:
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+ undici-types@7.25.0:
+ resolution: {integrity: sha512-AXNgS1Byr27fTI+2bsPEkV9CxkT8H6xNyRI68b3TatlZo3RkzlqQBLL+w7SmGPVpokjHbcuNVQUWE7FRTg+LRA==}
+
unicorn-magic@0.3.0:
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
engines: {node: '>=18'}
@@ -9274,6 +9228,13 @@ snapshots:
'@types/istanbul-lib-coverage@2.0.6': {}
+ '@types/jsdom@28.0.1':
+ dependencies:
+ '@types/node': 24.11.0
+ '@types/tough-cookie': 4.0.5
+ parse5: 7.3.0
+ undici-types: 7.25.0
+
'@types/json-schema@7.0.15': {}
'@types/mdx@2.0.13': {}
@@ -9304,6 +9265,8 @@ snapshots:
'@types/statuses@2.0.6': {}
+ '@types/tough-cookie@4.0.5': {}
+
'@types/validate-npm-package-name@4.0.2': {}
'@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)':
@@ -9905,8 +9868,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- boolbase@1.0.0: {}
-
brace-expansion@1.1.12:
dependencies:
balanced-match: 1.0.2
@@ -10106,27 +10067,15 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
- css-select@5.2.2:
- dependencies:
- boolbase: 1.0.0
- css-what: 6.2.2
- domhandler: 5.0.3
- domutils: 3.2.2
- nth-check: 2.1.1
-
css-tree@3.1.0:
dependencies:
mdn-data: 2.12.2
source-map-js: 1.2.1
- css-what@6.2.2: {}
-
css.escape@1.5.1: {}
cssesc@3.0.0: {}
- cssom@0.5.0: {}
-
cssstyle@4.6.0:
dependencies:
'@asamuzakjp/css-color': 3.2.0
@@ -10243,24 +10192,6 @@ snapshots:
dom-accessibility-api@0.6.3: {}
- dom-serializer@2.0.0:
- dependencies:
- domelementtype: 2.3.0
- domhandler: 5.0.3
- entities: 4.5.0
-
- domelementtype@2.3.0: {}
-
- domhandler@5.0.3:
- dependencies:
- domelementtype: 2.3.0
-
- domutils@3.2.2:
- dependencies:
- dom-serializer: 2.0.0
- domelementtype: 2.3.0
- domhandler: 5.0.3
-
dotenv-cli@7.4.2:
dependencies:
cross-spawn: 7.0.6
@@ -10322,12 +10253,8 @@ snapshots:
ansi-colors: 4.1.3
strip-ansi: 6.0.1
- entities@4.5.0: {}
-
entities@6.0.1: {}
- entities@7.0.1: {}
-
env-paths@2.2.1: {}
environment@1.1.0: {}
@@ -11146,19 +11073,10 @@ snapshots:
html-escaper@2.0.2: {}
- html-escaper@3.0.3: {}
-
html-parse-stringify@3.0.1:
dependencies:
void-elements: 3.1.0
- htmlparser2@10.1.0:
- dependencies:
- domelementtype: 2.3.0
- domhandler: 5.0.3
- domutils: 3.2.2
- entities: 7.0.1
-
http-errors@2.0.1:
dependencies:
depd: 2.0.0
@@ -11699,14 +11617,6 @@ snapshots:
lines-and-columns@1.2.4: {}
- linkedom@0.18.12:
- dependencies:
- css-select: 5.2.2
- cssom: 0.5.0
- html-escaper: 3.0.3
- htmlparser2: 10.1.0
- uhyphen: 0.2.0
-
lint-staged@16.2.5:
dependencies:
commander: 14.0.1
@@ -11981,10 +11891,6 @@ snapshots:
path-key: 4.0.0
unicorn-magic: 0.3.0
- nth-check@2.1.1:
- dependencies:
- boolbase: 1.0.0
-
nwsapi@2.2.22: {}
object-assign@4.1.1: {}
@@ -13267,8 +13173,6 @@ snapshots:
ufo@1.6.1: {}
- uhyphen@0.2.0: {}
-
unbox-primitive@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -13278,6 +13182,8 @@ snapshots:
undici-types@7.16.0: {}
+ undici-types@7.25.0: {}
+
unicorn-magic@0.3.0: {}
universalify@0.1.2: {}