diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 09f91e6..fade885 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -16,10 +16,24 @@ jobs:
- run: yarn install
- run: yarn lint-test
- build:
+ test:
runs-on: ubuntu-latest
env:
CI_JOB_NUMBER: 2
+ steps:
+ - uses: actions/checkout@v1
+ - name: Use Node.js from .nvmrc
+ uses: actions/setup-node@v6
+ with:
+ node-version-file: '.nvmrc'
+ - run: corepack enable
+ - run: yarn install
+ - run: yarn workspace @hawk.so/javascript test
+
+ build:
+ runs-on: ubuntu-latest
+ env:
+ CI_JOB_NUMBER: 3
steps:
- uses: actions/checkout@v1
with:
diff --git a/packages/javascript/README.md b/packages/javascript/README.md
index c541694..8cd86fc 100644
--- a/packages/javascript/README.md
+++ b/packages/javascript/README.md
@@ -89,7 +89,7 @@ Initialization settings:
| `disableVueErrorHandler` | boolean | optional | Do not initialize Vue errors handling |
| `consoleTracking` | boolean | optional | Initialize console logs tracking |
| `breadcrumbs` | false or BreadcrumbsOptions object | optional | Configure breadcrumbs tracking (see below) |
-| `beforeSend` | function(event) => event | optional | This Method allows you to filter any data you don't want sending to Hawk |
+| `beforeSend` | function(event) => event \| false \| void | optional | Filter data before sending. Return modified event, `false` to drop the event. |
Other available [initial settings](types/hawk-initial-settings.d.ts) are described at the type definition.
@@ -187,7 +187,7 @@ const hawk = new HawkCatcher({
beforeBreadcrumb: (breadcrumb, hint) => {
// Filter or modify breadcrumbs before storing
if (breadcrumb.category === 'fetch' && breadcrumb.data?.url?.includes('/sensitive')) {
- return null; // Discard this breadcrumb
+ return false; // Discard this breadcrumb
}
return breadcrumb;
}
@@ -203,7 +203,7 @@ const hawk = new HawkCatcher({
| `trackFetch` | `boolean` | `true` | Automatically track `fetch()` and `XMLHttpRequest` calls as breadcrumbs. Captures request URL, method, status code, and response time. |
| `trackNavigation` | `boolean` | `true` | Automatically track navigation events (History API: `pushState`, `replaceState`, `popstate`). Captures route changes. |
| `trackClicks` | `boolean` | `true` | Automatically track UI click events. Captures element selector, coordinates, and other click metadata. |
-| `beforeBreadcrumb` | `function` | `undefined` | Hook called before each breadcrumb is stored. Receives `(breadcrumb, hint)` and can return modified breadcrumb, `null` to discard it, or the original breadcrumb. Useful for filtering sensitive data or PII. |
+| `beforeBreadcrumb` | `function` | `undefined` | Hook called before each breadcrumb is stored. Receives `(breadcrumb, hint)`. Return modified breadcrumb to keep it, `false` to discard. |
### Manual Breadcrumbs
diff --git a/packages/javascript/example/index.html b/packages/javascript/example/index.html
index 91f6185..c8a050b 100644
--- a/packages/javascript/example/index.html
+++ b/packages/javascript/example/index.html
@@ -312,7 +312,7 @@
Test Vue integration: <test-component>
// beforeBreadcrumb: (breadcrumb, hint) => {
// // Filter or modify breadcrumbs before storing
// if (breadcrumb.category === 'fetch' && breadcrumb.data?.url?.includes('/sensitive')) {
- // return null; // Discard this breadcrumb
+ // return false; // Discard this breadcrumb
// }
// return breadcrumb;
// }
diff --git a/packages/javascript/package.json b/packages/javascript/package.json
index a9d4b44..dfb7b94 100644
--- a/packages/javascript/package.json
+++ b/packages/javascript/package.json
@@ -1,6 +1,6 @@
{
"name": "@hawk.so/javascript",
- "version": "3.2.15",
+ "version": "3.2.16",
"description": "JavaScript errors tracking for Hawk.so",
"files": [
"dist"
@@ -20,6 +20,8 @@
"dev": "vite",
"build": "vite build",
"stats": "size-limit > stats.txt",
+ "test": "vitest run",
+ "test:watch": "vitest",
"lint": "eslint --fix \"src/**/*.{js,ts}\""
},
"repository": {
@@ -40,9 +42,11 @@
"error-stack-parser": "^2.1.4"
},
"devDependencies": {
- "@hawk.so/types": "0.5.2",
+ "@hawk.so/types": "0.5.8",
+ "jsdom": "^28.0.0",
"vite": "^7.3.1",
"vite-plugin-dts": "^4.2.4",
+ "vitest": "^4.0.18",
"vue": "^2"
}
}
diff --git a/packages/javascript/src/addons/breadcrumbs.ts b/packages/javascript/src/addons/breadcrumbs.ts
index 8c5361a..10a7a96 100644
--- a/packages/javascript/src/addons/breadcrumbs.ts
+++ b/packages/javascript/src/addons/breadcrumbs.ts
@@ -5,6 +5,7 @@ import type { Breadcrumb, BreadcrumbLevel, BreadcrumbType, Json, JsonNode } from
import Sanitizer from '../modules/sanitizer';
import { buildElementSelector } from '../utils/selector';
import log from '../utils/log';
+import { isValidBreadcrumb } from '../utils/validation';
/**
* Default maximum number of breadcrumbs to store
@@ -48,11 +49,13 @@ export interface BreadcrumbsOptions {
maxBreadcrumbs?: number;
/**
- * Hook called before each breadcrumb is stored
- * Return null to discard the breadcrumb
- * Return modified breadcrumb to store it
+ * Hook called before each breadcrumb is stored.
+ * - Return modified breadcrumb — it will be stored instead of the original.
+ * - Return `false` — the breadcrumb will be discarded.
+ * - Return nothing (`void` / `undefined` / `null`) — the original breadcrumb is stored as-is (a warning is logged).
+ * - If the hook returns an invalid value, a warning is logged and the original breadcrumb is stored.
*/
- beforeBreadcrumb?: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => Breadcrumb | null;
+ beforeBreadcrumb?: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => Breadcrumb | false | void;
/**
* Enable automatic fetch/XHR breadcrumbs
@@ -91,7 +94,7 @@ interface InternalBreadcrumbsOptions {
trackFetch: boolean;
trackNavigation: boolean;
trackClicks: boolean;
- beforeBreadcrumb?: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => Breadcrumb | null;
+ beforeBreadcrumb?: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => Breadcrumb | false | void;
}
/**
@@ -233,16 +236,30 @@ export class BreadcrumbManager {
* Apply beforeBreadcrumb hook
*/
if (this.options.beforeBreadcrumb) {
- const result = this.options.beforeBreadcrumb(bc, hint);
+ const breadcrumbClone = structuredClone(bc);
+ const result = this.options.beforeBreadcrumb(breadcrumbClone, hint);
- if (result === null) {
- /**
- * Discard breadcrumb
- */
+ /**
+ * false means discard
+ */
+ if (result === false) {
return;
}
- Object.assign(bc, result);
+ /**
+ * Valid breadcrumb → apply changes from hook
+ */
+ if (isValidBreadcrumb(result)) {
+ Object.assign(bc, result);
+ } else {
+ /**
+ * Anything else is invalid — warn, bc stays untouched (hook only received a clone)
+ */
+ log(
+ 'Invalid beforeBreadcrumb value. It should return breadcrumb or false. Breadcrumb is stored without changes.',
+ 'warn'
+ );
+ }
}
/**
diff --git a/packages/javascript/src/addons/consoleCatcher.ts b/packages/javascript/src/addons/consoleCatcher.ts
index 16815ab..29519ea 100644
--- a/packages/javascript/src/addons/consoleCatcher.ts
+++ b/packages/javascript/src/addons/consoleCatcher.ts
@@ -150,7 +150,7 @@ export class ConsoleCatcher {
* This ensures DevTools will navigate to the user's code, not the interceptor's code.
*
* @param errorStack - Full stack trace string from Error.stack
- * @returns Object with userStack (full stack from user code) and fileLine (first frame for DevTools link)
+ * @returns {object} Object with userStack (full stack from user code) and fileLine (first frame for DevTools link)
*/
private extractUserStack(errorStack: string | undefined): {
userStack: string;
@@ -250,7 +250,7 @@ export class ConsoleCatcher {
* 4. Store it in the buffer
* 5. Forward the call to the native console (so output still appears in DevTools)
*
- * @param {...any} args
+ * @param args - console method arguments
*/
window.console[method] = (...args: unknown[]): void => {
// Capture full stack trace and extract user code stack
diff --git a/packages/javascript/src/catcher.ts b/packages/javascript/src/catcher.ts
index 313ba93..0646507 100644
--- a/packages/javascript/src/catcher.ts
+++ b/packages/javascript/src/catcher.ts
@@ -2,7 +2,7 @@ import Socket from './modules/socket';
import Sanitizer from './modules/sanitizer';
import log from './utils/log';
import StackParser from './modules/stackParser';
-import type { CatcherMessage, HawkInitialSettings, BreadcrumbsAPI } from './types';
+import type { CatcherMessage, HawkInitialSettings, BreadcrumbsAPI, Transport } from './types';
import { VueIntegration } from './integrations/vue';
import { id } from './utils/id';
import type {
@@ -10,7 +10,7 @@ import type {
EventContext,
JavaScriptAddons,
VueIntegrationAddons,
- Json, EncodedIntegrationToken, DecodedIntegrationToken,
+ Json, EncodedIntegrationToken, DecodedIntegrationToken
} from '@hawk.so/types';
import type { JavaScriptCatcherIntegrations } from './types/integrations';
import { EventRejectedError } from './errors';
@@ -18,7 +18,7 @@ import type { HawkJavaScriptEvent } from './types';
import { isErrorProcessed, markErrorAsProcessed } from './utils/event';
import { ConsoleCatcher } from './addons/consoleCatcher';
import { BreadcrumbManager } from './addons/breadcrumbs';
-import { validateUser, validateContext } from './utils/validation';
+import { validateUser, validateContext, isValidEventPayload } from './utils/validation';
/**
* Allow to use global VERSION, that will be overwritten by Webpack
@@ -73,16 +73,18 @@ export default class Catcher {
private context: EventContext | undefined;
/**
- * This Method allows developer to filter any data you don't want sending to Hawk
- * If method returns false, event will not be sent
+ * This Method allows developer to filter any data you don't want sending to Hawk.
+ * - Return modified event — it will be sent instead of the original.
+ * - Return `false` — the event will be dropped entirely.
+ * - Any other value is invalid — the original event is sent as-is (a warning is logged).
*/
- private readonly beforeSend: undefined | ((event: HawkJavaScriptEvent) => HawkJavaScriptEvent | false);
+ private readonly beforeSend: undefined | ((event: HawkJavaScriptEvent) => HawkJavaScriptEvent | false | void);
/**
* Transport for dialog between Catcher and Collector
- * (WebSocket decorator)
+ * (WebSocket decorator by default, or custom via settings.transport)
*/
- private readonly transport: Socket;
+ private readonly transport: Transport;
/**
* Module for parsing backtrace
@@ -148,7 +150,7 @@ export default class Catcher {
/**
* Init transport
*/
- this.transport = new Socket({
+ this.transport = settings.transport ?? new Socket({
collectorEndpoint: settings.collectorEndpoint || `wss://${this.getIntegrationId()}.k1.hawk.so:443/ws`,
reconnectionAttempts: settings.reconnectionAttempts,
reconnectionTimeout: settings.reconnectionTimeout,
@@ -436,12 +438,29 @@ export default class Catcher {
* Filter sensitive data
*/
if (typeof this.beforeSend === 'function') {
- const beforeSendResult = this.beforeSend(payload);
+ const eventPayloadClone = structuredClone(payload);
+ const result = this.beforeSend(eventPayloadClone);
- if (beforeSendResult === false) {
+ /**
+ * false → drop event
+ */
+ if (result === false) {
throw new EventRejectedError('Event rejected by beforeSend method.');
+ }
+
+ /**
+ * Valid event payload → use it instead of original
+ */
+ if (isValidEventPayload(result)) {
+ payload = result as HawkJavaScriptEvent;
} else {
- payload = beforeSendResult;
+ /**
+ * Anything else is invalid — warn, payload stays untouched (hook only received a clone)
+ */
+ log(
+ 'Invalid beforeSend value. It should return event or false. Event is sent without changes.',
+ 'warn'
+ );
}
}
diff --git a/packages/javascript/src/modules/socket.ts b/packages/javascript/src/modules/socket.ts
index c07af1d..eb64b59 100644
--- a/packages/javascript/src/modules/socket.ts
+++ b/packages/javascript/src/modules/socket.ts
@@ -1,12 +1,13 @@
import log from '../utils/log';
import type { CatcherMessage } from '@/types';
+import type { Transport } from '../types/transport';
/**
* Custom WebSocket wrapper class
*
* @copyright CodeX
*/
-export default class Socket {
+export default class Socket implements Transport {
/**
* Socket connection endpoint
*/
diff --git a/packages/javascript/src/types/hawk-initial-settings.ts b/packages/javascript/src/types/hawk-initial-settings.ts
index 51e80f0..987cdf4 100644
--- a/packages/javascript/src/types/hawk-initial-settings.ts
+++ b/packages/javascript/src/types/hawk-initial-settings.ts
@@ -1,5 +1,6 @@
import type { EventContext, AffectedUser } from '@hawk.so/types';
import type { HawkJavaScriptEvent } from './event';
+import type { Transport } from './transport';
import type { BreadcrumbsOptions } from '../addons/breadcrumbs';
/**
@@ -65,10 +66,11 @@ export interface HawkInitialSettings {
/**
* This Method allows you to filter any data you don't want sending to Hawk.
- *
- * Return `false` to prevent the event from being sent to Hawk.
+ * - Return modified event — it will be sent instead of the original.
+ * - Return `false` — the event will be dropped entirely.
+ * - Any other value is invalid — the original event is sent as-is (a warning is logged).
*/
- beforeSend?(event: HawkJavaScriptEvent): HawkJavaScriptEvent | false;
+ beforeSend?(event: HawkJavaScriptEvent): HawkJavaScriptEvent | false | void;
/**
* Disable Vue.js error handler
@@ -90,4 +92,10 @@ export interface HawkInitialSettings {
* @default enabled with default options
*/
breadcrumbs?: false | BreadcrumbsOptions;
+
+ /**
+ * Custom transport for sending events.
+ * If not provided, default WebSocket transport is used.
+ */
+ transport?: Transport;
}
diff --git a/packages/javascript/src/types/index.ts b/packages/javascript/src/types/index.ts
index ef0556e..f3354c3 100644
--- a/packages/javascript/src/types/index.ts
+++ b/packages/javascript/src/types/index.ts
@@ -1,5 +1,6 @@
import type { CatcherMessage } from './catcher-message';
import type { HawkInitialSettings } from './hawk-initial-settings';
+import type { Transport } from './transport';
import type { HawkJavaScriptEvent } from './event';
import type { VueIntegrationData, NuxtIntegrationData, NuxtIntegrationAddons, JavaScriptCatcherIntegrations } from './integrations';
import type { BreadcrumbsAPI } from './breadcrumbs-api';
@@ -7,6 +8,7 @@ import type { BreadcrumbsAPI } from './breadcrumbs-api';
export type {
CatcherMessage,
HawkInitialSettings,
+ Transport,
HawkJavaScriptEvent,
VueIntegrationData,
NuxtIntegrationData,
diff --git a/packages/javascript/src/types/transport.ts b/packages/javascript/src/types/transport.ts
new file mode 100644
index 0000000..f2237dc
--- /dev/null
+++ b/packages/javascript/src/types/transport.ts
@@ -0,0 +1,8 @@
+import type { CatcherMessage } from './catcher-message';
+
+/**
+ * Transport interface — anything that can send a CatcherMessage
+ */
+export interface Transport {
+ send(message: CatcherMessage): Promise;
+}
diff --git a/packages/javascript/src/utils/selector.ts b/packages/javascript/src/utils/selector.ts
index d886884..f14aedb 100644
--- a/packages/javascript/src/utils/selector.ts
+++ b/packages/javascript/src/utils/selector.ts
@@ -4,13 +4,14 @@
*
* @param element - HTML element to build selector from
* @param maxDepth - Maximum recursion depth (default: 3)
- * @returns CSS selector string (e.g., "div#myId.class1.class2" or ".some-parent button")
+ * @returns {string} CSS selector string (e.g., "div#myId.class1.class2" or ".some-parent button")
*/
export function buildElementSelector(element: HTMLElement, maxDepth: number = 3): string {
let selector = element.tagName.toLowerCase();
if (element.id) {
selector += `#${element.id}`;
+
return selector;
}
@@ -23,7 +24,9 @@ export function buildElementSelector(element: HTMLElement, maxDepth: number = 3)
const classNameStr = String(element.className);
if (classNameStr) {
- selector += `.${classNameStr.split(' ').filter(Boolean).join('.')}`;
+ selector += `.${classNameStr.split(' ').filter(Boolean)
+ .join('.')}`;
+
return selector;
}
}
diff --git a/packages/javascript/src/utils/validation.ts b/packages/javascript/src/utils/validation.ts
index 8db0e0b..c0f9f66 100644
--- a/packages/javascript/src/utils/validation.ts
+++ b/packages/javascript/src/utils/validation.ts
@@ -1,11 +1,11 @@
import log from './log';
-import type { AffectedUser, EventContext } from '@hawk.so/types';
+import type { AffectedUser, Breadcrumb, EventContext, EventData, JavaScriptAddons } from '@hawk.so/types';
import Sanitizer from '../modules/sanitizer';
/**
* Validates user data - basic security checks
*
- * @param user
+ * @param user - user data to validate
*/
export function validateUser(user: AffectedUser): boolean {
if (!user || !Sanitizer.isObject(user)) {
@@ -27,7 +27,7 @@ export function validateUser(user: AffectedUser): boolean {
/**
* Validates context data - basic security checks
*
- * @param context
+ * @param context - context data to validate
*/
export function validateContext(context: EventContext | undefined): boolean {
if (context && !Sanitizer.isObject(context)) {
@@ -38,3 +38,57 @@ export function validateContext(context: EventContext | undefined): boolean {
return true;
}
+
+/**
+ * Checks if value is a plain object (not array, Date, etc.)
+ *
+ * @param value - value to check
+ */
+function isPlainObject(value: unknown): value is Record {
+ return Object.prototype.toString.call(value) === '[object Object]';
+}
+
+/**
+ * Runtime check for required EventData fields.
+ * Per @hawk.so/types EventData, `title` is the only non-optional field.
+ * Additionally validates `backtrace` shape if present (must be an array).
+ *
+ * @param payload - value to validate
+ */
+export function isValidEventPayload(payload: unknown): payload is EventData {
+ if (!isPlainObject(payload)) {
+ return false;
+ }
+
+ if (typeof payload.title !== 'string' || payload.title.trim() === '') {
+ return false;
+ }
+
+ if (payload.backtrace !== undefined && !Array.isArray(payload.backtrace)) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Runtime check that value is a valid Breadcrumb-like object.
+ * Must be a plain object with a string message and numeric timestamp.
+ *
+ * @param breadcrumb - value to validate
+ */
+export function isValidBreadcrumb(breadcrumb: unknown): breadcrumb is Breadcrumb {
+ if (!isPlainObject(breadcrumb)) {
+ return false;
+ }
+
+ if (typeof breadcrumb.message !== 'string' || breadcrumb.message.trim() === '') {
+ return false;
+ }
+
+ if (breadcrumb.timestamp !== undefined && typeof breadcrumb.timestamp !== 'number') {
+ return false;
+ }
+
+ return true;
+}
diff --git a/packages/javascript/tests/before-send.test.ts b/packages/javascript/tests/before-send.test.ts
new file mode 100644
index 0000000..e9652a1
--- /dev/null
+++ b/packages/javascript/tests/before-send.test.ts
@@ -0,0 +1,162 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
+import type { CatcherMessage } from '../src/types/catcher-message';
+import type { Transport } from '../src/types/transport';
+import type { HawkJavaScriptEvent } from '../src/types/event';
+import Catcher from '../src/catcher';
+
+const TEST_TOKEN = 'eyJpbnRlZ3JhdGlvbklkIjoiOTU3MmQyOWQtNWJhZS00YmYyLTkwN2MtZDk5ZDg5MGIwOTVmIiwic2VjcmV0IjoiZTExODFiZWItMjdlMS00ZDViLWEwZmEtZmUwYTM1Mzg5OWMyIn0=';
+/**
+ * Wait for fire-and-forget async calls inside hawk.send() to complete
+ */
+const wait = (): Promise => new Promise((r) => setTimeout(r, 0));
+
+function createTransport() {
+ const sendSpy = vi.fn<(msg: CatcherMessage) => Promise>().mockResolvedValue(undefined);
+ const transport: Transport = { send: sendSpy };
+
+ return { sendSpy, transport };
+}
+
+function getSentPayload(spy: ReturnType): HawkJavaScriptEvent | null {
+ const calls = spy.mock.calls;
+
+ return calls.length ? calls[calls.length - 1][0].payload : null;
+}
+
+/**
+ * Shared Catcher config — no breadcrumbs, no global handlers, fake transport
+ */
+function createCatcher(transport: Transport, beforeSend: NonNullable[0] extends object ? ConstructorParameters[0]['beforeSend'] : never>) {
+ return new Catcher({
+ token: TEST_TOKEN,
+ disableGlobalErrorsHandling: true,
+ breadcrumbs: false,
+ transport,
+ beforeSend,
+ });
+}
+
+describe('beforeSend', () => {
+ let warnSpy: ReturnType;
+
+ beforeEach(() => {
+ warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ warnSpy.mockRestore();
+ });
+
+ it('should send event as-is when beforeSend returns it unchanged', async () => {
+ // Arrange
+ const { sendSpy, transport } = createTransport();
+ const hawk = createCatcher(transport, (event) => event);
+
+ // Act
+ hawk.send(new Error('hello'));
+ await wait();
+
+ // Assert
+ expect(sendSpy).toHaveBeenCalledOnce();
+ expect(getSentPayload(sendSpy)!.title).toBe('hello');
+ });
+
+ it('should send modified event when beforeSend mutates and returns it', async () => {
+ // Arrange
+ const { sendSpy, transport } = createTransport();
+ const hawk = createCatcher(transport, (event) => {
+ event.context = { sanitized: true };
+
+ return event;
+ });
+
+ // Act
+ hawk.send(new Error('modify'));
+ await wait();
+
+ // Assert
+ expect(sendSpy).toHaveBeenCalledOnce();
+ expect(getSentPayload(sendSpy)!.context).toEqual({ sanitized: true });
+ });
+
+ it('should not send event when beforeSend returns false', async () => {
+ // Arrange
+ const { sendSpy, transport } = createTransport();
+ const hawk = createCatcher(transport, () => false);
+
+ // Act
+ hawk.send(new Error('drop'));
+ await wait();
+
+ // Assert
+ expect(sendSpy).not.toHaveBeenCalled();
+ });
+
+ it.each([
+ { label: 'undefined', value: undefined },
+ { label: 'null', value: null },
+ { label: 'number (42)', value: 42 },
+ { label: 'string ("oops")', value: 'oops' },
+ { label: 'true', value: true },
+ ])('should send original event and warn when beforeSend returns $label', async ({ value }) => {
+ // Arrange
+ const { sendSpy, transport } = createTransport();
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const hawk = createCatcher(transport, () => value as any);
+
+ // Act
+ hawk.send(new Error('invalid'));
+ await wait();
+
+ // Assert
+ expect(sendSpy).toHaveBeenCalledOnce();
+ expect(getSentPayload(sendSpy)!.title).toBe('invalid');
+ expect(warnSpy).toHaveBeenCalledWith(
+ expect.stringContaining('Invalid beforeSend value'),
+ expect.anything(),
+ expect.anything()
+ );
+ });
+
+ it('should send original event and warn when beforeSend deletes required field (title)', async () => {
+ // Arrange
+ const { sendSpy, transport } = createTransport();
+ const hawk = createCatcher(transport, (event) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ delete (event as any).title;
+
+ return event;
+ });
+
+ // Act
+ hawk.send(new Error('required-field'));
+ await wait();
+
+ // Assert — fallback to original payload, title preserved
+ expect(sendSpy).toHaveBeenCalledOnce();
+ expect(getSentPayload(sendSpy)!.title).toBe('required-field');
+ expect(warnSpy).toHaveBeenCalledWith(
+ expect.stringContaining('Invalid beforeSend value'),
+ expect.anything(),
+ expect.anything()
+ );
+ });
+
+ it('should send event without deleted optional fields', async () => {
+ // Arrange
+ const { sendSpy, transport } = createTransport();
+ const hawk = createCatcher(transport, (event) => {
+ delete event.release;
+
+ return event;
+ });
+
+ // Act
+ hawk.send(new Error('optional'));
+ await wait();
+
+ // Assert
+ expect(sendSpy).toHaveBeenCalledOnce();
+ expect(getSentPayload(sendSpy)!.release).toBeUndefined();
+ });
+});
diff --git a/packages/javascript/tests/breadcrumbs.test.ts b/packages/javascript/tests/breadcrumbs.test.ts
new file mode 100644
index 0000000..f02d7dc
--- /dev/null
+++ b/packages/javascript/tests/breadcrumbs.test.ts
@@ -0,0 +1,233 @@
+import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
+import { BreadcrumbManager } from '../src/addons/breadcrumbs';
+import type { Breadcrumb } from '@hawk.so/types';
+
+function resetManager(): void {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (BreadcrumbManager as any).instance = null;
+}
+
+describe('BreadcrumbManager', () => {
+ let warnSpy: ReturnType;
+
+ beforeEach(() => {
+ resetManager();
+ warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ warnSpy.mockRestore();
+ });
+
+ it('should return empty array when no breadcrumbs added', () => {
+ const m = BreadcrumbManager.getInstance();
+
+ m.init();
+ expect(m.getBreadcrumbs()).toEqual([]);
+ });
+
+ it('should store breadcrumb with auto-generated timestamp', () => {
+ const m = BreadcrumbManager.getInstance();
+
+ m.init();
+ m.addBreadcrumb({ type: 'default', message: 'test', level: 'info' });
+
+ const crumbs = m.getBreadcrumbs();
+
+ expect(crumbs).toHaveLength(1);
+ expect(crumbs[0].message).toBe('test');
+ expect(crumbs[0].timestamp).toBeTypeOf('number');
+ });
+
+ it('should keep explicit timestamp as-is', () => {
+ const m = BreadcrumbManager.getInstance();
+
+ m.init();
+ m.addBreadcrumb({ type: 'default', message: 'test', level: 'info', timestamp: 12345 });
+
+ expect(m.getBreadcrumbs()[0].timestamp).toBe(12345);
+ });
+
+ it('should drop oldest breadcrumbs when buffer overflows (FIFO)', () => {
+ const m = BreadcrumbManager.getInstance();
+
+ m.init({ maxBreadcrumbs: 3 });
+
+ for (let i = 0; i < 5; i++) {
+ m.addBreadcrumb({ type: 'default', message: `msg-${i}`, level: 'info' });
+ }
+
+ const crumbs = m.getBreadcrumbs();
+
+ expect(crumbs).toHaveLength(3);
+ expect(crumbs[0].message).toBe('msg-2');
+ expect(crumbs[2].message).toBe('msg-4');
+ });
+
+ it('should store max 15 breadcrumbs by default', () => {
+ const m = BreadcrumbManager.getInstance();
+
+ m.init();
+
+ for (let i = 0; i < 20; i++) {
+ m.addBreadcrumb({ type: 'default', message: `msg-${i}`, level: 'info' });
+ }
+
+ expect(m.getBreadcrumbs()).toHaveLength(15);
+ });
+
+ it('should empty buffer on clear', () => {
+ const m = BreadcrumbManager.getInstance();
+
+ m.init();
+ m.addBreadcrumb({ type: 'default', message: 'test', level: 'info' });
+ m.clearBreadcrumbs();
+
+ expect(m.getBreadcrumbs()).toEqual([]);
+ });
+
+ it('should return a copy, not the internal array', () => {
+ const m = BreadcrumbManager.getInstance();
+
+ m.init();
+ m.addBreadcrumb({ type: 'default', message: 'test', level: 'info' });
+
+ const first = m.getBreadcrumbs();
+ const second = m.getBreadcrumbs();
+
+ expect(first).not.toBe(second);
+ expect(first).toEqual(second);
+
+ first.push({ type: 'default', message: 'injected', level: 'info', timestamp: 0 } as Breadcrumb);
+
+ expect(m.getBreadcrumbs()).toHaveLength(1);
+ });
+
+ it('should ignore second init call', () => {
+ const m = BreadcrumbManager.getInstance();
+
+ m.init({ maxBreadcrumbs: 5 });
+ m.init({ maxBreadcrumbs: 100 });
+
+ for (let i = 0; i < 10; i++) {
+ m.addBreadcrumb({ type: 'default', message: `msg-${i}`, level: 'info' });
+ }
+
+ expect(m.getBreadcrumbs()).toHaveLength(5);
+ });
+});
+
+describe('beforeBreadcrumb', () => {
+ let warnSpy: ReturnType;
+
+ beforeEach(() => {
+ resetManager();
+ warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ warnSpy.mockRestore();
+ });
+
+ it('should store modified breadcrumb when hook returns changed object', () => {
+ // Arrange
+ const m = BreadcrumbManager.getInstance();
+
+ m.init({
+ beforeBreadcrumb(bc) {
+ bc.message = 'MODIFIED';
+
+ return bc;
+ },
+ });
+
+ // Act
+ m.addBreadcrumb({ type: 'default', message: 'original', level: 'info' });
+
+ // Assert
+ expect(m.getBreadcrumbs()[0].message).toBe('MODIFIED');
+ });
+
+ it('should not store breadcrumb when hook returns false', () => {
+ // Arrange
+ const m = BreadcrumbManager.getInstance();
+
+ m.init({
+ beforeBreadcrumb: () => false,
+ });
+
+ // Act
+ m.addBreadcrumb({ type: 'default', message: 'drop', level: 'info' });
+
+ // Assert
+ expect(m.getBreadcrumbs()).toHaveLength(0);
+ });
+
+ it.each([
+ { label: 'undefined', value: undefined },
+ { label: 'null', value: null },
+ { label: 'number (42)', value: 42 },
+ { label: 'string ("oops")', value: 'oops' },
+ { label: 'true', value: true },
+ ])('should store original breadcrumb and warn when hook returns $label', ({ value }) => {
+ // Arrange
+ const m = BreadcrumbManager.getInstance();
+
+ m.init({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ beforeBreadcrumb: () => value as any,
+ });
+
+ // Act
+ m.addBreadcrumb({ type: 'default', message: 'original', level: 'info' });
+
+ // Assert
+ expect(m.getBreadcrumbs()[0].message).toBe('original');
+ expect(warnSpy).toHaveBeenCalledWith(
+ expect.stringContaining('Invalid beforeBreadcrumb value'),
+ expect.anything(),
+ expect.anything()
+ );
+ });
+
+ it('should store original breadcrumb and warn when hook deletes required field (message)', () => {
+ // Arrange
+ const m = BreadcrumbManager.getInstance();
+
+ m.init({
+ beforeBreadcrumb(bc) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ delete (bc as any).message;
+
+ return bc;
+ },
+ });
+
+ // Act
+ m.addBreadcrumb({ type: 'default', message: 'keep-me', level: 'info' });
+
+ // Assert — fallback to original, message preserved
+ expect(m.getBreadcrumbs()[0].message).toBe('keep-me');
+ });
+
+ it('should filter breadcrumbs by category using hook', () => {
+ // Arrange
+ const m = BreadcrumbManager.getInstance();
+
+ m.init({
+ beforeBreadcrumb(bc) {
+ return bc.category === 'secret' ? false : bc;
+ },
+ });
+
+ // Act
+ m.addBreadcrumb({ type: 'default', message: 'public', level: 'info', category: 'public' });
+ m.addBreadcrumb({ type: 'default', message: 'secret', level: 'info', category: 'secret' });
+
+ // Assert
+ const crumbs = m.getBreadcrumbs();
+
+ expect(crumbs).toHaveLength(1);
+ expect(crumbs[0].message).toBe('public');
+ });
+});
diff --git a/packages/javascript/tsconfig.test.json b/packages/javascript/tsconfig.test.json
new file mode 100644
index 0000000..166c447
--- /dev/null
+++ b/packages/javascript/tsconfig.test.json
@@ -0,0 +1,12 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": null,
+ "declaration": false
+ },
+ "include": [
+ "src/**/*",
+ "tests/**/*",
+ "vitest.config.ts"
+ ]
+}
diff --git a/packages/javascript/vitest.config.ts b/packages/javascript/vitest.config.ts
new file mode 100644
index 0000000..47ad6a2
--- /dev/null
+++ b/packages/javascript/vitest.config.ts
@@ -0,0 +1,18 @@
+import { defineConfig } from 'vitest/config';
+import path from 'path';
+
+export default defineConfig({
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ include: ['tests/**/*.test.ts'],
+ },
+ define: {
+ VERSION: JSON.stringify('0.0.0-test'),
+ },
+ resolve: {
+ alias: {
+ '@/types': path.resolve(__dirname, './src/types'),
+ },
+ },
+});
diff --git a/yarn.lock b/yarn.lock
index 88bec46..317217e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5,6 +5,46 @@ __metadata:
version: 8
cacheKey: 10c0
+"@acemir/cssom@npm:^0.9.31":
+ version: 0.9.31
+ resolution: "@acemir/cssom@npm:0.9.31"
+ checksum: 10c0/cbfff98812642104ec3b37de1ad3a53f216ddc437e7b9276a23f46f2453844ea3c3f46c200bc4656a2f747fb26567560b3cc5183d549d119a758926551b5f566
+ languageName: node
+ linkType: hard
+
+"@asamuzakjp/css-color@npm:^4.1.1":
+ version: 4.1.2
+ resolution: "@asamuzakjp/css-color@npm:4.1.2"
+ dependencies:
+ "@csstools/css-calc": "npm:^3.0.0"
+ "@csstools/css-color-parser": "npm:^4.0.1"
+ "@csstools/css-parser-algorithms": "npm:^4.0.0"
+ "@csstools/css-tokenizer": "npm:^4.0.0"
+ lru-cache: "npm:^11.2.5"
+ checksum: 10c0/e432fdef978b37654a2ca31169a149b9173e708f70c82612acb123a36dbc7dd99913c48cbf2edd6fe3652cc627d4bc94bf87571463da0b788f15b973d4ce5b0f
+ languageName: node
+ linkType: hard
+
+"@asamuzakjp/dom-selector@npm:^6.7.6":
+ version: 6.7.8
+ resolution: "@asamuzakjp/dom-selector@npm:6.7.8"
+ dependencies:
+ "@asamuzakjp/nwsapi": "npm:^2.3.9"
+ bidi-js: "npm:^1.0.3"
+ css-tree: "npm:^3.1.0"
+ is-potential-custom-element-name: "npm:^1.0.1"
+ lru-cache: "npm:^11.2.5"
+ checksum: 10c0/4274e5025e6e399654cb066f33a165f4dc65596a33612b0a345dce80666ad1f234b68b8b6db6f005fc76be365ea36e09bd7b08990442461f390c77b19cfea885
+ languageName: node
+ linkType: hard
+
+"@asamuzakjp/nwsapi@npm:^2.3.9":
+ version: 2.3.9
+ resolution: "@asamuzakjp/nwsapi@npm:2.3.9"
+ checksum: 10c0/869b81382e775499c96c45c6dbe0d0766a6da04bcf0abb79f5333535c4e19946851acaa43398f896e2ecc5a1de9cf3db7cf8c4b1afac1ee3d15e21584546d74d
+ languageName: node
+ linkType: hard
+
"@babel/code-frame@npm:7.12.11":
version: 7.12.11
resolution: "@babel/code-frame@npm:7.12.11"
@@ -61,6 +101,59 @@ __metadata:
languageName: node
linkType: hard
+"@csstools/color-helpers@npm:^6.0.1":
+ version: 6.0.1
+ resolution: "@csstools/color-helpers@npm:6.0.1"
+ checksum: 10c0/866844267d5aa5a02fe9d54f6db6fc18f6306595edb03664cc8ef15c99d3e6f3b42eb1a413c98bafa5b2dc0d8e0193da9b3bcc9d6a04f5de74cbd44935e74b3c
+ languageName: node
+ linkType: hard
+
+"@csstools/css-calc@npm:^3.0.0":
+ version: 3.0.1
+ resolution: "@csstools/css-calc@npm:3.0.1"
+ peerDependencies:
+ "@csstools/css-parser-algorithms": ^4.0.0
+ "@csstools/css-tokenizer": ^4.0.0
+ checksum: 10c0/ccda2b5d78ce6fecce58c59cc5747f5a8db6b5cae9b6d8a38444169d2d6591bf1f5e8bf147e5248e55e28a736e09be26a52621009089987786d69c209df39e0c
+ languageName: node
+ linkType: hard
+
+"@csstools/css-color-parser@npm:^4.0.1":
+ version: 4.0.1
+ resolution: "@csstools/css-color-parser@npm:4.0.1"
+ dependencies:
+ "@csstools/color-helpers": "npm:^6.0.1"
+ "@csstools/css-calc": "npm:^3.0.0"
+ peerDependencies:
+ "@csstools/css-parser-algorithms": ^4.0.0
+ "@csstools/css-tokenizer": ^4.0.0
+ checksum: 10c0/c46be5b9f5c0ef3cd25b47a71bd2a4d1c4856b123ecba4abe8eaa0688d3fc47f58fa67ea281d6b9efca4b9fdfa88fb045c51d0f9b8c612a56bd546d38260b138
+ languageName: node
+ linkType: hard
+
+"@csstools/css-parser-algorithms@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "@csstools/css-parser-algorithms@npm:4.0.0"
+ peerDependencies:
+ "@csstools/css-tokenizer": ^4.0.0
+ checksum: 10c0/94558c2428d6ef0ddef542e86e0a8376aa1263a12a59770abb13ba50d7b83086822c75433f32aa2e7fef00555e1cc88292f9ca5bce79aed232bb3fed73b1528d
+ languageName: node
+ linkType: hard
+
+"@csstools/css-syntax-patches-for-csstree@npm:^1.0.21":
+ version: 1.0.27
+ resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.27"
+ checksum: 10c0/ef3f2a639109758c0f3c04520465800ca4c830174bd6f7979795083877c82ace51ab8353857b06a818cb6c0de6d4dc88f84a86fc3b021be47f11a0f1c4b74e7e
+ languageName: node
+ linkType: hard
+
+"@csstools/css-tokenizer@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "@csstools/css-tokenizer@npm:4.0.0"
+ checksum: 10c0/669cf3d0f9c8e1ffdf8c9955ad8beba0c8cfe03197fe29a4fcbd9ee6f7a18856cfa42c62670021a75183d9ab37f5d14a866e6a9df753a6c07f59e36797a9ea9f
+ languageName: node
+ linkType: hard
+
"@es-joy/jsdoccomment@npm:~0.41.0":
version: 0.41.0
resolution: "@es-joy/jsdoccomment@npm:0.41.0"
@@ -471,14 +564,28 @@ __metadata:
languageName: node
linkType: hard
+"@exodus/bytes@npm:^1.11.0, @exodus/bytes@npm:^1.6.0":
+ version: 1.13.0
+ resolution: "@exodus/bytes@npm:1.13.0"
+ peerDependencies:
+ "@noble/hashes": ^1.8.0 || ^2.0.0
+ peerDependenciesMeta:
+ "@noble/hashes":
+ optional: true
+ checksum: 10c0/f854f2685544c9695f0f92583c65e35ed9f85dff1921ad17de6bf4d24d53075f31ce15a30402a3205958e63ec70211dbf525e97cdd874b157948814f9f2b208d
+ languageName: node
+ linkType: hard
+
"@hawk.so/javascript@npm:^3.0.0, @hawk.so/javascript@workspace:packages/javascript":
version: 0.0.0-use.local
resolution: "@hawk.so/javascript@workspace:packages/javascript"
dependencies:
- "@hawk.so/types": "npm:0.5.2"
+ "@hawk.so/types": "npm:0.5.8"
error-stack-parser: "npm:^2.1.4"
+ jsdom: "npm:^28.0.0"
vite: "npm:^7.3.1"
vite-plugin-dts: "npm:^4.2.4"
+ vitest: "npm:^4.0.18"
vue: "npm:^2"
languageName: unknown
linkType: soft
@@ -506,12 +613,12 @@ __metadata:
languageName: unknown
linkType: soft
-"@hawk.so/types@npm:0.5.2":
- version: 0.5.2
- resolution: "@hawk.so/types@npm:0.5.2"
+"@hawk.so/types@npm:0.5.8":
+ version: 0.5.8
+ resolution: "@hawk.so/types@npm:0.5.8"
dependencies:
bson: "npm:^7.0.0"
- checksum: 10c0/4a76aebc3bc4c87649d4a9991f51c81fac2ad457896ba19261afa66d8979739bed6108db322122400e99b3ca81cb4fbc3be7dee9b7c334d3cbfef51c2ec57719
+ checksum: 10c0/95d45be58594b30aad097d25beb800e2d236cf869b3f57916a67bea841263cab96eecf4d678dd86f94ec46318759b9d566c42228a450c1d05689b3f326654d53
languageName: node
linkType: hard
@@ -1094,6 +1201,16 @@ __metadata:
languageName: node
linkType: hard
+"@types/chai@npm:^5.2.2":
+ version: 5.2.3
+ resolution: "@types/chai@npm:5.2.3"
+ dependencies:
+ "@types/deep-eql": "npm:*"
+ assertion-error: "npm:^2.0.1"
+ checksum: 10c0/e0ef1de3b6f8045a5e473e867c8565788c444271409d155588504840ad1a53611011f85072188c2833941189400228c1745d78323dac13fcede9c2b28bacfb2f
+ languageName: node
+ linkType: hard
+
"@types/cookie@npm:^0.6.0":
version: 0.6.0
resolution: "@types/cookie@npm:0.6.0"
@@ -1101,6 +1218,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/deep-eql@npm:*":
+ version: 4.0.2
+ resolution: "@types/deep-eql@npm:4.0.2"
+ checksum: 10c0/bf3f811843117900d7084b9d0c852da9a044d12eb40e6de73b552598a6843c21291a8a381b0532644574beecd5e3491c5ff3a0365ab86b15d59862c025384844
+ languageName: node
+ linkType: hard
+
"@types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.6":
version: 1.0.8
resolution: "@types/estree@npm:1.0.8"
@@ -1252,6 +1376,86 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/expect@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/expect@npm:4.0.18"
+ dependencies:
+ "@standard-schema/spec": "npm:^1.0.0"
+ "@types/chai": "npm:^5.2.2"
+ "@vitest/spy": "npm:4.0.18"
+ "@vitest/utils": "npm:4.0.18"
+ chai: "npm:^6.2.1"
+ tinyrainbow: "npm:^3.0.3"
+ checksum: 10c0/123b0aa111682e82ec5289186df18037b1a1768700e468ee0f9879709aaa320cf790463c15c0d8ee10df92b402f4394baf5d27797e604d78e674766d87bcaadc
+ languageName: node
+ linkType: hard
+
+"@vitest/mocker@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/mocker@npm:4.0.18"
+ dependencies:
+ "@vitest/spy": "npm:4.0.18"
+ estree-walker: "npm:^3.0.3"
+ magic-string: "npm:^0.30.21"
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^6.0.0 || ^7.0.0-0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+ checksum: 10c0/fb0a257e7e167759d4ad228d53fa7bad2267586459c4a62188f2043dd7163b4b02e1e496dc3c227837f776e7d73d6c4343613e89e7da379d9d30de8260f1ee4b
+ languageName: node
+ linkType: hard
+
+"@vitest/pretty-format@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/pretty-format@npm:4.0.18"
+ dependencies:
+ tinyrainbow: "npm:^3.0.3"
+ checksum: 10c0/0086b8c88eeca896d8e4b98fcdef452c8041a1b63eb9e85d3e0bcc96c8aa76d8e9e0b6990ebb0bb0a697c4ebab347e7735888b24f507dbff2742ddce7723fd94
+ languageName: node
+ linkType: hard
+
+"@vitest/runner@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/runner@npm:4.0.18"
+ dependencies:
+ "@vitest/utils": "npm:4.0.18"
+ pathe: "npm:^2.0.3"
+ checksum: 10c0/fdb4afa411475133c05ba266c8092eaf1e56cbd5fb601f92ec6ccb9bab7ca52e06733ee8626599355cba4ee71cb3a8f28c84d3b69dc972e41047edc50229bc01
+ languageName: node
+ linkType: hard
+
+"@vitest/snapshot@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/snapshot@npm:4.0.18"
+ dependencies:
+ "@vitest/pretty-format": "npm:4.0.18"
+ magic-string: "npm:^0.30.21"
+ pathe: "npm:^2.0.3"
+ checksum: 10c0/d3bfefa558db9a69a66886ace6575eb96903a5ba59f4d9a5d0fecb4acc2bb8dbb443ef409f5ac1475f2e1add30bd1d71280f98912da35e89c75829df9e84ea43
+ languageName: node
+ linkType: hard
+
+"@vitest/spy@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/spy@npm:4.0.18"
+ checksum: 10c0/6de537890b3994fcadb8e8d8ac05942320ae184f071ec395d978a5fba7fa928cbb0c5de85af86a1c165706c466e840de8779eaff8c93450c511c7abaeb9b8a4e
+ languageName: node
+ linkType: hard
+
+"@vitest/utils@npm:4.0.18":
+ version: 4.0.18
+ resolution: "@vitest/utils@npm:4.0.18"
+ dependencies:
+ "@vitest/pretty-format": "npm:4.0.18"
+ tinyrainbow: "npm:^3.0.3"
+ checksum: 10c0/4a3c43c1421eb90f38576926496f6c80056167ba111e63f77cf118983902673737a1a38880b890d7c06ec0a12475024587344ee502b3c43093781533022f2aeb
+ languageName: node
+ linkType: hard
+
"@volar/language-core@npm:2.4.28, @volar/language-core@npm:~2.4.11":
version: 2.4.28
resolution: "@volar/language-core@npm:2.4.28"
@@ -1626,6 +1830,13 @@ __metadata:
languageName: node
linkType: hard
+"assertion-error@npm:^2.0.1":
+ version: 2.0.1
+ resolution: "assertion-error@npm:2.0.1"
+ checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8
+ languageName: node
+ linkType: hard
+
"astral-regex@npm:^2.0.0":
version: 2.0.0
resolution: "astral-regex@npm:2.0.0"
@@ -1670,6 +1881,15 @@ __metadata:
languageName: node
linkType: hard
+"bidi-js@npm:^1.0.3":
+ version: 1.0.3
+ resolution: "bidi-js@npm:1.0.3"
+ dependencies:
+ require-from-string: "npm:^2.0.2"
+ checksum: 10c0/fdddea4aa4120a34285486f2267526cd9298b6e8b773ad25e765d4f104b6d7437ab4ba542e6939e3ac834a7570bcf121ee2cf6d3ae7cd7082c4b5bedc8f271e1
+ languageName: node
+ linkType: hard
+
"brace-expansion@npm:^1.1.7":
version: 1.1.12
resolution: "brace-expansion@npm:1.1.12"
@@ -1786,6 +2006,13 @@ __metadata:
languageName: node
linkType: hard
+"chai@npm:^6.2.1":
+ version: 6.2.2
+ resolution: "chai@npm:6.2.2"
+ checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53
+ languageName: node
+ linkType: hard
+
"chalk@npm:^2.4.2":
version: 2.4.2
resolution: "chalk@npm:2.4.2"
@@ -1922,6 +2149,16 @@ __metadata:
languageName: node
linkType: hard
+"css-tree@npm:^3.1.0":
+ version: 3.1.0
+ resolution: "css-tree@npm:3.1.0"
+ dependencies:
+ mdn-data: "npm:2.12.2"
+ source-map-js: "npm:^1.0.1"
+ checksum: 10c0/b5715852c2f397c715ca00d56ec53fc83ea596295ae112eb1ba6a1bda3b31086380e596b1d8c4b980fe6da09e7d0fc99c64d5bb7313030dd0fba9c1415f30979
+ languageName: node
+ linkType: hard
+
"cssesc@npm:^3.0.0":
version: 3.0.0
resolution: "cssesc@npm:3.0.0"
@@ -1931,6 +2168,18 @@ __metadata:
languageName: node
linkType: hard
+"cssstyle@npm:^5.3.7":
+ version: 5.3.7
+ resolution: "cssstyle@npm:5.3.7"
+ dependencies:
+ "@asamuzakjp/css-color": "npm:^4.1.1"
+ "@csstools/css-syntax-patches-for-csstree": "npm:^1.0.21"
+ css-tree: "npm:^3.1.0"
+ lru-cache: "npm:^11.2.4"
+ checksum: 10c0/9330f014f4209df06305264b92b8e963dfef636fdc2ae7d13f24ea7da6468aba1dc5eb13082621258bdd22cbd7fb7cb291894e188a3cdf660e8b79cd2c5e5e0e
+ languageName: node
+ linkType: hard
+
"csstype@npm:^3.1.0":
version: 3.2.3
resolution: "csstype@npm:3.2.3"
@@ -1938,6 +2187,16 @@ __metadata:
languageName: node
linkType: hard
+"data-urls@npm:^7.0.0":
+ version: 7.0.0
+ resolution: "data-urls@npm:7.0.0"
+ dependencies:
+ whatwg-mimetype: "npm:^5.0.0"
+ whatwg-url: "npm:^16.0.0"
+ checksum: 10c0/08d88ef50d8966a070ffdaa703e1e4b29f01bb2da364dfbc1612b1c2a4caa8045802c9532d81347b21781100132addb36a585071c8323b12cce97973961dee9f
+ languageName: node
+ linkType: hard
+
"data-view-buffer@npm:^1.0.2":
version: 1.0.2
resolution: "data-view-buffer@npm:1.0.2"
@@ -1999,6 +2258,13 @@ __metadata:
languageName: node
linkType: hard
+"decimal.js@npm:^10.6.0":
+ version: 10.6.0
+ resolution: "decimal.js@npm:10.6.0"
+ checksum: 10c0/07d69fbcc54167a340d2d97de95f546f9ff1f69d2b45a02fd7a5292412df3cd9eb7e23065e532a318f5474a2e1bccf8392fdf0443ef467f97f3bf8cb0477e5aa
+ languageName: node
+ linkType: hard
+
"deep-is@npm:^0.1.3":
version: 0.1.4
resolution: "deep-is@npm:0.1.4"
@@ -2113,6 +2379,13 @@ __metadata:
languageName: node
linkType: hard
+"entities@npm:^6.0.0":
+ version: 6.0.1
+ resolution: "entities@npm:6.0.1"
+ checksum: 10c0/ed836ddac5acb34341094eb495185d527bd70e8632b6c0d59548cbfa23defdbae70b96f9a405c82904efa421230b5b3fd2283752447d737beffd3f3e6ee74414
+ languageName: node
+ linkType: hard
+
"entities@npm:^7.0.0":
version: 7.0.1
resolution: "entities@npm:7.0.1"
@@ -2219,6 +2492,13 @@ __metadata:
languageName: node
linkType: hard
+"es-module-lexer@npm:^1.7.0":
+ version: 1.7.0
+ resolution: "es-module-lexer@npm:1.7.0"
+ checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b
+ languageName: node
+ linkType: hard
+
"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1":
version: 1.1.1
resolution: "es-object-atoms@npm:1.1.1"
@@ -2833,6 +3113,15 @@ __metadata:
languageName: node
linkType: hard
+"estree-walker@npm:^3.0.3":
+ version: 3.0.3
+ resolution: "estree-walker@npm:3.0.3"
+ dependencies:
+ "@types/estree": "npm:^1.0.0"
+ checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d
+ languageName: node
+ linkType: hard
+
"esutils@npm:^2.0.2, esutils@npm:^2.0.3":
version: 2.0.3
resolution: "esutils@npm:2.0.3"
@@ -2840,6 +3129,13 @@ __metadata:
languageName: node
linkType: hard
+"expect-type@npm:^1.2.2":
+ version: 1.3.0
+ resolution: "expect-type@npm:1.3.0"
+ checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd
+ languageName: node
+ linkType: hard
+
"exponential-backoff@npm:^3.1.1":
version: 3.1.3
resolution: "exponential-backoff@npm:3.1.3"
@@ -3288,6 +3584,15 @@ __metadata:
languageName: node
linkType: hard
+"html-encoding-sniffer@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "html-encoding-sniffer@npm:6.0.0"
+ dependencies:
+ "@exodus/bytes": "npm:^1.6.0"
+ checksum: 10c0/66dc3f6f5539cc3beb814fcbfae7eacf4ec38cf824d6e1425b72039b51a40f4456bd8541ba66f4f4fe09cdf885ab5cd5bae6ec6339d6895a930b2fdb83c53025
+ languageName: node
+ linkType: hard
+
"http-cache-semantics@npm:^4.1.1":
version: 4.2.0
resolution: "http-cache-semantics@npm:4.2.0"
@@ -3295,7 +3600,7 @@ __metadata:
languageName: node
linkType: hard
-"http-proxy-agent@npm:^7.0.0":
+"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.2":
version: 7.0.2
resolution: "http-proxy-agent@npm:7.0.2"
dependencies:
@@ -3305,7 +3610,7 @@ __metadata:
languageName: node
linkType: hard
-"https-proxy-agent@npm:^7.0.1":
+"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.6":
version: 7.0.6
resolution: "https-proxy-agent@npm:7.0.6"
dependencies:
@@ -3562,6 +3867,13 @@ __metadata:
languageName: node
linkType: hard
+"is-potential-custom-element-name@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "is-potential-custom-element-name@npm:1.0.1"
+ checksum: 10c0/b73e2f22bc863b0939941d369486d308b43d7aef1f9439705e3582bfccaa4516406865e32c968a35f97a99396dac84e2624e67b0a16b0a15086a785e16ce7db9
+ languageName: node
+ linkType: hard
+
"is-reference@npm:^3.0.3":
version: 3.0.3
resolution: "is-reference@npm:3.0.3"
@@ -3718,6 +4030,39 @@ __metadata:
languageName: node
linkType: hard
+"jsdom@npm:^28.0.0":
+ version: 28.0.0
+ resolution: "jsdom@npm:28.0.0"
+ dependencies:
+ "@acemir/cssom": "npm:^0.9.31"
+ "@asamuzakjp/dom-selector": "npm:^6.7.6"
+ "@exodus/bytes": "npm:^1.11.0"
+ cssstyle: "npm:^5.3.7"
+ data-urls: "npm:^7.0.0"
+ decimal.js: "npm:^10.6.0"
+ html-encoding-sniffer: "npm:^6.0.0"
+ http-proxy-agent: "npm:^7.0.2"
+ https-proxy-agent: "npm:^7.0.6"
+ is-potential-custom-element-name: "npm:^1.0.1"
+ parse5: "npm:^8.0.0"
+ saxes: "npm:^6.0.0"
+ symbol-tree: "npm:^3.2.4"
+ tough-cookie: "npm:^6.0.0"
+ undici: "npm:^7.20.0"
+ w3c-xmlserializer: "npm:^5.0.0"
+ webidl-conversions: "npm:^8.0.1"
+ whatwg-mimetype: "npm:^5.0.0"
+ whatwg-url: "npm:^16.0.0"
+ xml-name-validator: "npm:^5.0.0"
+ peerDependencies:
+ canvas: ^3.0.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+ checksum: 10c0/6aa2419506f912f40c5f1c6ca52c6dfdfde5970cfbaf105ebfc55ab975dda2d2492b6f8dc4c62b94e46501c4f77dfd2a60ea229ee67f924d59fe6c51bf653043
+ languageName: node
+ linkType: hard
+
"json-buffer@npm:3.0.1":
version: 3.0.1
resolution: "json-buffer@npm:3.0.1"
@@ -3870,6 +4215,13 @@ __metadata:
languageName: node
linkType: hard
+"lru-cache@npm:^11.2.4, lru-cache@npm:^11.2.5":
+ version: 11.2.6
+ resolution: "lru-cache@npm:11.2.6"
+ checksum: 10c0/73bbffb298760e71b2bfe8ebc16a311c6a60ceddbba919cfedfd8635c2d125fbfb5a39b71818200e67973b11f8d59c5a9e31d6f90722e340e90393663a66e5cd
+ languageName: node
+ linkType: hard
+
"lru-cache@npm:^6.0.0":
version: 6.0.0
resolution: "lru-cache@npm:6.0.0"
@@ -3914,6 +4266,13 @@ __metadata:
languageName: node
linkType: hard
+"mdn-data@npm:2.12.2":
+ version: 2.12.2
+ resolution: "mdn-data@npm:2.12.2"
+ checksum: 10c0/b22443b71d70f72ccc3c6ba1608035431a8fc18c3c8fc53523f06d20e05c2ac10f9b53092759a2ca85cf02f0d37036f310b581ce03e7b99ac74d388ef8152ade
+ languageName: node
+ linkType: hard
+
"merge2@npm:^1.3.0, merge2@npm:^1.4.1":
version: 1.4.1
resolution: "merge2@npm:1.4.1"
@@ -4241,7 +4600,7 @@ __metadata:
languageName: node
linkType: hard
-"obug@npm:^2.1.0":
+"obug@npm:^2.1.0, obug@npm:^2.1.1":
version: 2.1.1
resolution: "obug@npm:2.1.1"
checksum: 10c0/59dccd7de72a047e08f8649e94c1015ec72f94eefb6ddb57fb4812c4b425a813bc7e7cd30c9aca20db3c59abc3c85cc7a62bb656a968741d770f4e8e02bc2e78
@@ -4305,6 +4664,15 @@ __metadata:
languageName: node
linkType: hard
+"parse5@npm:^8.0.0":
+ version: 8.0.0
+ resolution: "parse5@npm:8.0.0"
+ dependencies:
+ entities: "npm:^6.0.0"
+ checksum: 10c0/8279892dcd77b2f2229707f60eb039e303adf0288812b2a8fd5acf506a4d432da833c6c5d07a6554bef722c2367a81ef4a1f7e9336564379a7dba3e798bf16b3
+ languageName: node
+ linkType: hard
+
"path-browserify@npm:^1.0.1":
version: 1.0.1
resolution: "path-browserify@npm:1.0.1"
@@ -4504,7 +4872,7 @@ __metadata:
languageName: node
linkType: hard
-"punycode@npm:^2.1.0":
+"punycode@npm:^2.1.0, punycode@npm:^2.3.1":
version: 2.3.1
resolution: "punycode@npm:2.3.1"
checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9
@@ -4808,6 +5176,15 @@ __metadata:
languageName: node
linkType: hard
+"saxes@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "saxes@npm:6.0.0"
+ dependencies:
+ xmlchars: "npm:^2.2.0"
+ checksum: 10c0/3847b839f060ef3476eb8623d099aa502ad658f5c40fd60c105ebce86d244389b0d76fcae30f4d0c728d7705ceb2f7e9b34bb54717b6a7dbedaf5dad2d9a4b74
+ languageName: node
+ linkType: hard
+
"semver@npm:^6.3.1":
version: 6.3.1
resolution: "semver@npm:6.3.1"
@@ -4945,6 +5322,13 @@ __metadata:
languageName: node
linkType: hard
+"siginfo@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "siginfo@npm:2.0.0"
+ checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34
+ languageName: node
+ linkType: hard
+
"sirv@npm:^3.0.0":
version: 3.0.2
resolution: "sirv@npm:3.0.2"
@@ -5019,7 +5403,7 @@ __metadata:
languageName: node
linkType: hard
-"source-map-js@npm:^1.2.1":
+"source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.1":
version: 1.2.1
resolution: "source-map-js@npm:1.2.1"
checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf
@@ -5121,6 +5505,13 @@ __metadata:
languageName: node
linkType: hard
+"stackback@npm:0.0.2":
+ version: 0.0.2
+ resolution: "stackback@npm:0.0.2"
+ checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983
+ languageName: node
+ linkType: hard
+
"stackframe@npm:^1.3.4":
version: 1.3.4
resolution: "stackframe@npm:1.3.4"
@@ -5128,6 +5519,13 @@ __metadata:
languageName: node
linkType: hard
+"std-env@npm:^3.10.0":
+ version: 3.10.0
+ resolution: "std-env@npm:3.10.0"
+ checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f
+ languageName: node
+ linkType: hard
+
"stop-iteration-iterator@npm:^1.1.0":
version: 1.1.0
resolution: "stop-iteration-iterator@npm:1.1.0"
@@ -5293,6 +5691,13 @@ __metadata:
languageName: node
linkType: hard
+"symbol-tree@npm:^3.2.4":
+ version: 3.2.4
+ resolution: "symbol-tree@npm:3.2.4"
+ checksum: 10c0/dfbe201ae09ac6053d163578778c53aa860a784147ecf95705de0cd23f42c851e1be7889241495e95c37cabb058edb1052f141387bef68f705afc8f9dd358509
+ languageName: node
+ linkType: hard
+
"table@npm:^6.0.9":
version: 6.9.0
resolution: "table@npm:6.9.0"
@@ -5326,6 +5731,20 @@ __metadata:
languageName: node
linkType: hard
+"tinybench@npm:^2.9.0":
+ version: 2.9.0
+ resolution: "tinybench@npm:2.9.0"
+ checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c
+ languageName: node
+ linkType: hard
+
+"tinyexec@npm:^1.0.2":
+ version: 1.0.2
+ resolution: "tinyexec@npm:1.0.2"
+ checksum: 10c0/1261a8e34c9b539a9aae3b7f0bb5372045ff28ee1eba035a2a059e532198fe1a182ec61ac60fa0b4a4129f0c4c4b1d2d57355b5cb9aa2d17ac9454ecace502ee
+ languageName: node
+ linkType: hard
+
"tinyglobby@npm:^0.2.11, tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15":
version: 0.2.15
resolution: "tinyglobby@npm:0.2.15"
@@ -5336,6 +5755,31 @@ __metadata:
languageName: node
linkType: hard
+"tinyrainbow@npm:^3.0.3":
+ version: 3.0.3
+ resolution: "tinyrainbow@npm:3.0.3"
+ checksum: 10c0/1e799d35cd23cabe02e22550985a3051dc88814a979be02dc632a159c393a998628eacfc558e4c746b3006606d54b00bcdea0c39301133956d10a27aa27e988c
+ languageName: node
+ linkType: hard
+
+"tldts-core@npm:^7.0.23":
+ version: 7.0.23
+ resolution: "tldts-core@npm:7.0.23"
+ checksum: 10c0/b3d936a75b5f65614c356a58ef37563681c6224187dcce9f57aac76d92aae83b1a6fe6ab910f77472b35456bc145a8441cb3e572b4850be43cb4f3465e0610ec
+ languageName: node
+ linkType: hard
+
+"tldts@npm:^7.0.5":
+ version: 7.0.23
+ resolution: "tldts@npm:7.0.23"
+ dependencies:
+ tldts-core: "npm:^7.0.23"
+ bin:
+ tldts: bin/cli.js
+ checksum: 10c0/492874770afaade724a10f8a97cce511d74bed07735c7f1100b7957254d7a5bbbc18becaf5cd049f9d7b0feeb945a64af64d5a300dfb851a4ac57cf3a5998afc
+ languageName: node
+ linkType: hard
+
"to-regex-range@npm:^5.0.1":
version: 5.0.1
resolution: "to-regex-range@npm:5.0.1"
@@ -5352,6 +5796,24 @@ __metadata:
languageName: node
linkType: hard
+"tough-cookie@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "tough-cookie@npm:6.0.0"
+ dependencies:
+ tldts: "npm:^7.0.5"
+ checksum: 10c0/7b17a461e9c2ac0d0bea13ab57b93b4346d0b8c00db174c963af1e46e4ea8d04148d2a55f2358fc857db0c0c65208a98e319d0c60693e32e0c559a9d9cf20cb5
+ languageName: node
+ linkType: hard
+
+"tr46@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "tr46@npm:6.0.0"
+ dependencies:
+ punycode: "npm:^2.3.1"
+ checksum: 10c0/83130df2f649228aa91c17754b66248030a3af34911d713b5ea417066fa338aa4bc8668d06bd98aa21a2210f43fc0a3db8b9099e7747fb5830e40e39a6a1058e
+ languageName: node
+ linkType: hard
+
"ts-api-utils@npm:^1.0.1":
version: 1.4.3
resolution: "ts-api-utils@npm:1.4.3"
@@ -5501,6 +5963,13 @@ __metadata:
languageName: node
linkType: hard
+"undici@npm:^7.20.0":
+ version: 7.21.0
+ resolution: "undici@npm:7.21.0"
+ checksum: 10c0/ec5d7524125ac9c392a8a67b84fe4f9301936ca6b2afd7be12e73ab98726e55761dc9624ac10361d2f346e6fdaf66043381a62f7d0b565facd61bbfda975a586
+ languageName: node
+ linkType: hard
+
"unique-filename@npm:^5.0.0":
version: 5.0.0
resolution: "unique-filename@npm:5.0.0"
@@ -5572,7 +6041,7 @@ __metadata:
languageName: node
linkType: hard
-"vite@npm:^7.3.1":
+"vite@npm:^6.0.0 || ^7.0.0, vite@npm:^7.3.1":
version: 7.3.1
resolution: "vite@npm:7.3.1"
dependencies:
@@ -5639,6 +6108,65 @@ __metadata:
languageName: node
linkType: hard
+"vitest@npm:^4.0.18":
+ version: 4.0.18
+ resolution: "vitest@npm:4.0.18"
+ dependencies:
+ "@vitest/expect": "npm:4.0.18"
+ "@vitest/mocker": "npm:4.0.18"
+ "@vitest/pretty-format": "npm:4.0.18"
+ "@vitest/runner": "npm:4.0.18"
+ "@vitest/snapshot": "npm:4.0.18"
+ "@vitest/spy": "npm:4.0.18"
+ "@vitest/utils": "npm:4.0.18"
+ es-module-lexer: "npm:^1.7.0"
+ expect-type: "npm:^1.2.2"
+ magic-string: "npm:^0.30.21"
+ obug: "npm:^2.1.1"
+ pathe: "npm:^2.0.3"
+ picomatch: "npm:^4.0.3"
+ std-env: "npm:^3.10.0"
+ tinybench: "npm:^2.9.0"
+ tinyexec: "npm:^1.0.2"
+ tinyglobby: "npm:^0.2.15"
+ tinyrainbow: "npm:^3.0.3"
+ vite: "npm:^6.0.0 || ^7.0.0"
+ why-is-node-running: "npm:^2.3.0"
+ peerDependencies:
+ "@edge-runtime/vm": "*"
+ "@opentelemetry/api": ^1.9.0
+ "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0
+ "@vitest/browser-playwright": 4.0.18
+ "@vitest/browser-preview": 4.0.18
+ "@vitest/browser-webdriverio": 4.0.18
+ "@vitest/ui": 4.0.18
+ happy-dom: "*"
+ jsdom: "*"
+ peerDependenciesMeta:
+ "@edge-runtime/vm":
+ optional: true
+ "@opentelemetry/api":
+ optional: true
+ "@types/node":
+ optional: true
+ "@vitest/browser-playwright":
+ optional: true
+ "@vitest/browser-preview":
+ optional: true
+ "@vitest/browser-webdriverio":
+ optional: true
+ "@vitest/ui":
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+ bin:
+ vitest: vitest.mjs
+ checksum: 10c0/b913cd32032c95f29ff08c931f4b4c6fd6d2da498908d6770952c561a1b8d75c62499a1f04cadf82fb89cc0f9a33f29fb5dfdb899f6dbb27686a9d91571be5fa
+ languageName: node
+ linkType: hard
+
"vscode-uri@npm:^3.0.8":
version: 3.1.0
resolution: "vscode-uri@npm:3.1.0"
@@ -5656,6 +6184,40 @@ __metadata:
languageName: node
linkType: hard
+"w3c-xmlserializer@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "w3c-xmlserializer@npm:5.0.0"
+ dependencies:
+ xml-name-validator: "npm:^5.0.0"
+ checksum: 10c0/8712774c1aeb62dec22928bf1cdfd11426c2c9383a1a63f2bcae18db87ca574165a0fbe96b312b73652149167ac6c7f4cf5409f2eb101d9c805efe0e4bae798b
+ languageName: node
+ linkType: hard
+
+"webidl-conversions@npm:^8.0.1":
+ version: 8.0.1
+ resolution: "webidl-conversions@npm:8.0.1"
+ checksum: 10c0/3f6f327ca5fa0c065ed8ed0ef3b72f33623376e68f958e9b7bd0df49fdb0b908139ac2338d19fb45bd0e05595bda96cb6d1622222a8b413daa38a17aacc4dd46
+ languageName: node
+ linkType: hard
+
+"whatwg-mimetype@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "whatwg-mimetype@npm:5.0.0"
+ checksum: 10c0/eead164fe73a00dd82f817af6fc0bd22e9c273e1d55bf4bc6bdf2da7ad8127fca82ef00ea6a37892f5f5641f8e34128e09508f92126086baba126b9e0d57feb4
+ languageName: node
+ linkType: hard
+
+"whatwg-url@npm:^16.0.0":
+ version: 16.0.0
+ resolution: "whatwg-url@npm:16.0.0"
+ dependencies:
+ "@exodus/bytes": "npm:^1.11.0"
+ tr46: "npm:^6.0.0"
+ webidl-conversions: "npm:^8.0.1"
+ checksum: 10c0/9b8cb392be244d0e9687ffe543f9ea63b7aa051a98547ea362a38d182d89bfbd96e13e7ed3f40df1f7566bb7c3581f6c081ddea950cf5382532716ce33000ff4
+ languageName: node
+ linkType: hard
+
"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1":
version: 1.1.1
resolution: "which-boxed-primitive@npm:1.1.1"
@@ -5739,6 +6301,18 @@ __metadata:
languageName: node
linkType: hard
+"why-is-node-running@npm:^2.3.0":
+ version: 2.3.0
+ resolution: "why-is-node-running@npm:2.3.0"
+ dependencies:
+ siginfo: "npm:^2.0.0"
+ stackback: "npm:0.0.2"
+ bin:
+ why-is-node-running: cli.js
+ checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054
+ languageName: node
+ linkType: hard
+
"word-wrap@npm:^1.2.5":
version: 1.2.5
resolution: "word-wrap@npm:1.2.5"
@@ -5753,6 +6327,20 @@ __metadata:
languageName: node
linkType: hard
+"xml-name-validator@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "xml-name-validator@npm:5.0.0"
+ checksum: 10c0/3fcf44e7b73fb18be917fdd4ccffff3639373c7cb83f8fc35df6001fecba7942f1dbead29d91ebb8315e2f2ff786b508f0c9dc0215b6353f9983c6b7d62cb1f5
+ languageName: node
+ linkType: hard
+
+"xmlchars@npm:^2.2.0":
+ version: 2.2.0
+ resolution: "xmlchars@npm:2.2.0"
+ checksum: 10c0/b64b535861a6f310c5d9bfa10834cf49127c71922c297da9d4d1b45eeaae40bf9b4363275876088fbe2667e5db028d2cd4f8ee72eed9bede840a67d57dab7593
+ languageName: node
+ linkType: hard
+
"yallist@npm:^4.0.0":
version: 4.0.0
resolution: "yallist@npm:4.0.0"