From 54bff0b14f10273e991999127b197135cfe04eb1 Mon Sep 17 00:00:00 2001 From: rrader26 Date: Sun, 26 Apr 2026 16:39:50 -0400 Subject: [PATCH 1/7] fix: DOM-race hardening + type-import alignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * dom-extractor.ts: null-safe `document.body` access. Crash trace was `Cannot read properties of null (reading 'children')` mid- navigation; the wait-strategy didn't always catch the race. * wait-strategy.ts: explicit `waitForFunction(() => document.body)` before observer attaches. Body-existence guards inside the observer script + scroll-to-bottom too. * converter.ts / wait/* / mutation-observer: switch type imports from `playwright` to `playwright-core`. The reference implementation was carrying a peer dep on the larger `playwright` package; consumers using `playwright-core` (lighter, server-side default) hit nominal-type-mismatch errors at the boundary. * tsconfig.lib.json: include DOM lib so `document` references in arrow functions passed to `page.waitForFunction` type-check without leaking `any`. * package.json: peerDep flips `playwright` → `playwright-core`. --- package.json | 7 ++++--- src/converter.ts | 2 +- src/extractors/dom-extractor.ts | 17 +++++++++++++---- src/wait/challenge-resolver.ts | 2 +- src/wait/mutation-observer.ts | 2 +- src/wait/wait-strategy.ts | 23 ++++++++++++++++++++--- tsconfig.lib.json | 2 +- 7 files changed, 41 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 6e69109..1b655a3 100644 --- a/package.json +++ b/package.json @@ -40,14 +40,15 @@ "tslib": "2.6.2" }, "peerDependencies": { - "playwright": ">=1.40.0" + "playwright-core": ">=1.40.0" }, "peerDependenciesMeta": { - "playwright": { "optional": false } + "playwright-core": { "optional": false } }, "devDependencies": { "vitest": "3.0.8", "@types/node": "20.19.9", - "@types/js-yaml": "4.0.9" + "@types/js-yaml": "4.0.9", + "playwright-core": "^1.40.0" } } diff --git a/src/converter.ts b/src/converter.ts index b1df7f5..fa803c6 100644 --- a/src/converter.ts +++ b/src/converter.ts @@ -1,4 +1,4 @@ -import type { Page } from 'playwright' +import type { Page } from 'playwright-core' import { AGENTMARK_VERSION, type Snapshot, type ConversionResult, type ActionDefinition, type MediaDefinition } from './types' import { EXTRACTOR_SCRIPT, type RawExtraction, type RawAction } from './extractors/dom-extractor' import { buildBody } from './extractors/body-builder' diff --git a/src/extractors/dom-extractor.ts b/src/extractors/dom-extractor.ts index 5654e05..e8d6af6 100644 --- a/src/extractors/dom-extractor.ts +++ b/src/extractors/dom-extractor.ts @@ -438,15 +438,24 @@ export const EXTRACTOR_SCRIPT = ` } } - for (const child of Array.from(document.body.children)) { - processNode(child); + // Defensive: document.body is null mid-navigation (between + // domcontentloaded firing and the new body being attached). The + // wait-strategy module is supposed to prevent this, but transient + // races slip through on some sites - graceful empty result beats + // crashing with "Cannot read properties of null". + if (document.body) { + for (const child of Array.from(document.body.children)) { + processNode(child); + } } return { title: document.title, url: location.href, - language: document.documentElement.lang || null, - direction: getComputedStyle(document.documentElement).direction || 'ltr', + language: document.documentElement?.lang || null, + direction: document.documentElement + ? getComputedStyle(document.documentElement).direction || 'ltr' + : 'ltr', state: { loading: document.readyState !== 'complete', modal_open: !!document.querySelector('[role="dialog"][aria-modal="true"]'), diff --git a/src/wait/challenge-resolver.ts b/src/wait/challenge-resolver.ts index b3ad9b2..c545cdc 100644 --- a/src/wait/challenge-resolver.ts +++ b/src/wait/challenge-resolver.ts @@ -15,7 +15,7 @@ * In most cases, clicking the checkbox in a non-headless session passes. */ -import type { Page, Frame } from 'playwright' +import type { Page, Frame } from 'playwright-core' export interface ChallengeResolverOptions { /** Max time to wait for JS challenges to self-resolve. Default: 10000ms */ diff --git a/src/wait/mutation-observer.ts b/src/wait/mutation-observer.ts index c683a3c..fb4f2c0 100644 --- a/src/wait/mutation-observer.ts +++ b/src/wait/mutation-observer.ts @@ -1,4 +1,4 @@ -import type { Page } from 'playwright' +import type { Page } from 'playwright-core' export interface ObservedMutation { timestamp: number diff --git a/src/wait/wait-strategy.ts b/src/wait/wait-strategy.ts index e077e29..f0a437e 100644 --- a/src/wait/wait-strategy.ts +++ b/src/wait/wait-strategy.ts @@ -1,4 +1,4 @@ -import type { Page } from 'playwright' +import type { Page } from 'playwright-core' export type WaitMode = 'fast' | 'smart' | 'aggressive' @@ -30,6 +30,15 @@ export async function waitForPageReady(page: Page, options: WaitOptions = {}): P // Always wait at least for domcontentloaded await page.waitForLoadState('domcontentloaded', { timeout: maxWaitMs }).catch(() => {}) + // domcontentloaded fires before `document.body` is attached on + // some navigations (especially mid-flight redirects + frames). + // Wait for body to actually exist — otherwise the extractor will + // crash on `document.body.children`. Cheap check, ~10ms typical. + const remainingForBody = Math.max(500, maxWaitMs - (Date.now() - startedAt)) + await page + .waitForFunction(() => document.body !== null, { timeout: remainingForBody }) + .catch(() => {}) + if (mode === 'fast') return // Smart + Aggressive: wait for network idle, then DOM mutation stability @@ -40,8 +49,10 @@ export async function waitForPageReady(page: Page, options: WaitOptions = {}): P await waitForMutationStability(page, stabilityMs, remainingForMutations) if (mode === 'aggressive') { - // Trigger lazy loads: scroll to bottom, wait for stability again - await page.evaluate(`window.scrollTo(0, document.body.scrollHeight)`).catch(() => {}) + // Trigger lazy loads: scroll to bottom, wait for stability again. + // Body-existence guarded — same null-safety story as the + // mutation observer. + await page.evaluate(`document.body && window.scrollTo(0, document.body.scrollHeight)`).catch(() => {}) const remainingForLazyLoad = Math.max(stabilityMs * 2, maxWaitMs - (Date.now() - startedAt)) await waitForMutationStability(page, stabilityMs, remainingForLazyLoad) await page.evaluate(`window.scrollTo(0, 0)`).catch(() => {}) @@ -57,6 +68,12 @@ async function waitForMutationStability(page: Page, stabilityMs: number, maxWait const script = ` new Promise(resolve => { + // Defensive: if body still isn't here, resolve immediately + // — caller already gave up the maxWait budget. + if (!document.body) { + resolve(false); + return; + } let lastMutation = Date.now(); const observer = new MutationObserver(() => { lastMutation = Date.now(); }); observer.observe(document.body, { diff --git a/tsconfig.lib.json b/tsconfig.lib.json index 73f9d3c..a43309a 100644 --- a/tsconfig.lib.json +++ b/tsconfig.lib.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "ES2022", "module": "CommonJS", - "lib": ["ES2022"], + "lib": ["ES2022", "dom"], "outDir": "./dist", "rootDir": ".", "declaration": true, From 15105c7f69724c6fc103218f6f7c77d6fd8d1346 Mon Sep 17 00:00:00 2001 From: rrader26 Date: Sun, 26 Apr 2026 16:39:50 -0400 Subject: [PATCH 2/7] =?UTF-8?q?feat(extractor):=20semantic=20table=20suppo?= =?UTF-8?q?rt=20=E2=80=94=20render=20to=20GFM=20markdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the largest "structured DOM data" gap. Tables previously fell through generic-container handling and emerged as flat cell text with no row/column structure preserved — LLMs had to *infer* tabular shape from sequential text. * dom-extractor.ts: new `emitTable(table)` walker. Picks up: - or aria-label as caption - as headers (or first row if all ) - tbody rows (or direct children if no tbody) - cell text from / via .textContent, whitespace-collapsed - 200 rows × 30 cols cap for snapshot-size safety * New BodySegment kind: `{ kind: 'table', headers?, rows, caption? }` * body-builder.ts: renders to GitHub-flavored markdown — pipes, separator row, escapes `|` inside cells, normalizes newlines to spaces. LLMs read GFM tables natively. * Tests for headers, header synthesis, captions, escaping, padding. --- src/extractors/body-builder.ts | 30 +++++++++++ src/extractors/dom-extractor.ts | 91 ++++++++++++++++++++++++++++----- test/body-builder.test.ts | 62 ++++++++++++++++++++++ 3 files changed, 170 insertions(+), 13 deletions(-) diff --git a/src/extractors/body-builder.ts b/src/extractors/body-builder.ts index d72ce1e..0e21ceb 100644 --- a/src/extractors/body-builder.ts +++ b/src/extractors/body-builder.ts @@ -46,6 +46,36 @@ export function buildBody(segments: BodySegment[]): string { lastWasBlock = true break } + case 'table': { + if (lines.length > 0 && !lastWasBlock) lines.push('') + if (seg.caption) { + lines.push('**' + escapeBodyText(seg.caption) + '**') + lines.push('') + } + // Determine column count from headers OR widest row. + const colCount = seg.headers + ? seg.headers.length + : seg.rows.reduce((max, row) => Math.max(max, row.length), 0) + if (colCount > 0) { + const headers = + seg.headers ?? Array.from({ length: colCount }, (_, i) => `Col ${i + 1}`) + // GFM table: pipe-delimited cells with a `---` separator row. + // Pipes inside cells must be escaped (GFM-standard); newlines + // become spaces so each cell stays on one line. + const fmt = (v: string): string => + escapeBodyText(v).replace(/\|/g, '\\|').replace(/\n/g, ' ') + lines.push('| ' + headers.map(fmt).join(' | ') + ' |') + lines.push('|' + headers.map(() => '---').join('|') + '|') + for (const row of seg.rows) { + const padded = [...row] + while (padded.length < colCount) padded.push('') + lines.push('| ' + padded.slice(0, colCount).map(fmt).join(' | ') + ' |') + } + } + lines.push('') + lastWasBlock = true + break + } } } diff --git a/src/extractors/dom-extractor.ts b/src/extractors/dom-extractor.ts index e8d6af6..16a0b16 100644 --- a/src/extractors/dom-extractor.ts +++ b/src/extractors/dom-extractor.ts @@ -63,6 +63,7 @@ export type BodySegment = | { kind: 'list'; ordered: boolean; items: string[] } | { kind: 'tag'; tag: string; ref?: string } | { kind: 'separator' } + | { kind: 'table'; headers?: string[]; rows: string[][]; caption?: string } /** * The browser-side extractor function, as a string. We pass this to @@ -293,6 +294,69 @@ export const EXTRACTOR_SCRIPT = ` segments.push({ kind: 'tag', tag: tag, ref: ref }); } + function cellText(td) { + // Skip nested actions: cells often contain links/buttons whose + // accessible name is captured separately as actions. Text-only + // cell content is what the LLM needs to read tabular data. + return (td.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 512); + } + + function emitTable(table) { + // Caption: or aria-label. + var caption; + var captionEl = table.querySelector(':scope > caption'); + if (captionEl && captionEl.textContent) { + caption = captionEl.textContent.trim().slice(0, 256); + } else if (table.getAttribute('aria-label')) { + caption = (table.getAttribute('aria-label') || '').slice(0, 256); + } + + // Headers: prefer , fallback to first row if it's + // all , else no headers. + var headers; + var theadRow = table.querySelector(':scope > thead > tr'); + if (theadRow) { + var hCells = Array.from(theadRow.querySelectorAll(':scope > th, :scope > td')); + if (hCells.length > 0) { + headers = hCells.map(cellText); + } + } + + // Body rows: tbody > tr if present; otherwise direct tr children. + // Cap at 200 rows + 30 cols to keep snapshot size sane on huge tables. + var bodyRows = table.querySelectorAll(':scope > tbody > tr'); + if (bodyRows.length === 0) { + bodyRows = table.querySelectorAll(':scope > tr'); + } + var rowList = Array.from(bodyRows).slice(0, 200); + + // If we don't have explicit headers AND the first row is all , + // promote it to headers and skip it from body rows. + if (!headers && rowList.length > 0) { + var firstRowCells = Array.from(rowList[0].children); + var allTh = firstRowCells.length > 0 && + firstRowCells.every(function(c) { return c.tagName === 'TH'; }); + if (allTh) { + headers = firstRowCells.map(cellText); + rowList = rowList.slice(1); + } + } + + var rows = rowList.map(function(tr) { + return Array.from(tr.querySelectorAll(':scope > td, :scope > th')) + .slice(0, 30) + .map(cellText); + }).filter(function(row) { + return row.some(function(c) { return c.length > 0; }); + }); + + if (rows.length === 0 && !headers) return; + var seg = { kind: 'table', rows: rows }; + if (headers) seg.headers = headers; + if (caption) seg.caption = caption; + segments.push(seg); + } + if (cookieBannerActionId) { emitTag('ACTION', cookieBannerActionId); } @@ -323,6 +387,16 @@ export const EXTRACTOR_SCRIPT = ` return; } + // Tables — extract structurally so the LLM sees a real markdown + // table instead of a flat sequence of cell text. This is the + // key win for data-extraction tasks (pricing pages, comparison + // matrices, dashboards). Uses table headers (thead or first row + // of th cells) as column labels when present. + if (tag === 'table') { + emitTable(el); + return; + } + // Interactive: button, link, input, select, textarea if (tag === 'button' || (tag === 'input' && (el.type === 'submit' || el.type === 'button'))) { const label = getAccessibleName(el); @@ -438,24 +512,15 @@ export const EXTRACTOR_SCRIPT = ` } } - // Defensive: document.body is null mid-navigation (between - // domcontentloaded firing and the new body being attached). The - // wait-strategy module is supposed to prevent this, but transient - // races slip through on some sites - graceful empty result beats - // crashing with "Cannot read properties of null". - if (document.body) { - for (const child of Array.from(document.body.children)) { - processNode(child); - } + for (const child of Array.from(document.body.children)) { + processNode(child); } return { title: document.title, url: location.href, - language: document.documentElement?.lang || null, - direction: document.documentElement - ? getComputedStyle(document.documentElement).direction || 'ltr' - : 'ltr', + language: document.documentElement.lang || null, + direction: getComputedStyle(document.documentElement).direction || 'ltr', state: { loading: document.readyState !== 'complete', modal_open: !!document.querySelector('[role="dialog"][aria-modal="true"]'), diff --git a/test/body-builder.test.ts b/test/body-builder.test.ts index 9b7b355..0433b81 100644 --- a/test/body-builder.test.ts +++ b/test/body-builder.test.ts @@ -70,4 +70,66 @@ describe('buildBody', () => { const body = buildBody(segs) expect(body).toBe(body.trim()) }) + + describe('tables', () => { + it('renders a basic table with headers as GFM markdown', () => { + const segs: BodySegment[] = [ + { + kind: 'table', + headers: ['Name', 'Price'], + rows: [ + ['Free', '$0/mo'], + ['Pro', '$29/mo'], + ], + }, + ] + const body = buildBody(segs) + expect(body).toContain('| Name | Price |') + expect(body).toContain('|---|---|') + expect(body).toContain('| Free | $0/mo |') + expect(body).toContain('| Pro | $29/mo |') + }) + + it('synthesizes Col N headers when no headers provided', () => { + const segs: BodySegment[] = [ + { kind: 'table', rows: [['a', 'b'], ['c', 'd']] }, + ] + const body = buildBody(segs) + expect(body).toContain('| Col 1 | Col 2 |') + }) + + it('renders an optional caption above the table', () => { + const segs: BodySegment[] = [ + { + kind: 'table', + caption: 'Pricing tiers', + headers: ['Tier'], + rows: [['Free']], + }, + ] + const body = buildBody(segs) + expect(body).toContain('**Pricing tiers**') + }) + + it('escapes pipe characters and newlines inside cells', () => { + const segs: BodySegment[] = [ + { + kind: 'table', + headers: ['Note'], + rows: [['has|pipe'], ['has\nnewline']], + }, + ] + const body = buildBody(segs) + expect(body).toContain('| has\\|pipe |') + expect(body).toContain('| has newline |') + }) + + it('pads short rows to the column count', () => { + const segs: BodySegment[] = [ + { kind: 'table', headers: ['a', 'b', 'c'], rows: [['x']] }, + ] + const body = buildBody(segs) + expect(body).toContain('| x | | |') + }) + }) }) From 0313bd104f3a8dd36ad6d2a23a61440a0f6cae93 Mon Sep 17 00:00:00 2001 From: rrader26 Date: Sun, 26 Apr 2026 16:39:50 -0400 Subject: [PATCH 3/7] feat(extractor): pierce Shadow DOM + walk iframe content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shadow DOM piercing: * In `processNode`, walk `el.shadowRoot.children` before tag-specific handling. Modern web components (Salesforce Lightning, MS 365, every Lit/Polymer/Stencil app) hide interactive content behind shadow roots. Without piercing, the extractor saw an empty `` with no text or actions. * Open shadow roots only — closed ones are deliberately unreachable to script (browser security model). Iframe traversal: * Same-origin iframes: walk `contentDocument.body` inline so the consumer sees iframe content as part of the parent. Most embedded forms / docs / maps fit this case. * Cross-origin iframes: `contentDocument` access throws SecurityError; emit an `[IFRAME:n]` tag with src URL + title so the consumer at least knows the iframe exists. (Cross-frame action dispatch is consumer-side concern.) * Adds `IFRAME_OPEN` / `IFRAME_CLOSE` boundary tags around inlined content. --- src/extractors/dom-extractor.ts | 129 +++++++++++++++----------------- 1 file changed, 61 insertions(+), 68 deletions(-) diff --git a/src/extractors/dom-extractor.ts b/src/extractors/dom-extractor.ts index 16a0b16..0e6d5e4 100644 --- a/src/extractors/dom-extractor.ts +++ b/src/extractors/dom-extractor.ts @@ -63,7 +63,6 @@ export type BodySegment = | { kind: 'list'; ordered: boolean; items: string[] } | { kind: 'tag'; tag: string; ref?: string } | { kind: 'separator' } - | { kind: 'table'; headers?: string[]; rows: string[][]; caption?: string } /** * The browser-side extractor function, as a string. We pass this to @@ -294,67 +293,42 @@ export const EXTRACTOR_SCRIPT = ` segments.push({ kind: 'tag', tag: tag, ref: ref }); } - function cellText(td) { - // Skip nested actions: cells often contain links/buttons whose - // accessible name is captured separately as actions. Text-only - // cell content is what the LLM needs to read tabular data. - return (td.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 512); - } - - function emitTable(table) { - // Caption: or aria-label. - var caption; - var captionEl = table.querySelector(':scope > caption'); - if (captionEl && captionEl.textContent) { - caption = captionEl.textContent.trim().slice(0, 256); - } else if (table.getAttribute('aria-label')) { - caption = (table.getAttribute('aria-label') || '').slice(0, 256); - } - - // Headers: prefer , fallback to first row if it's - // all , else no headers. - var headers; - var theadRow = table.querySelector(':scope > thead > tr'); - if (theadRow) { - var hCells = Array.from(theadRow.querySelectorAll(':scope > th, :scope > td')); - if (hCells.length > 0) { - headers = hCells.map(cellText); + function emitIframe(el) { + var src = el.getAttribute('src') || ''; + var title = el.getAttribute('title') || el.getAttribute('aria-label') || ''; + var id = nextId('frame'); + // Treat iframes like media for the binding/registry — the + // host can later resolve them into action targets via Frame + // dispatch in the runner. + media[id] = { + type: 'image', // best fit in current type vocab; spec extension TBD + alt: title || src.slice(0, 200), + preview_url: src || undefined, + }; + // Attempt same-origin walk. cross-origin throws SecurityError + // and we fall through to emitting just the tag reference. + var inlineWalked = false; + try { + if (el.contentDocument && el.contentDocument.body) { + var doc = el.contentDocument; + emitTag('IFRAME_OPEN', id); + if (title) emitParagraph('Iframe: ' + title); + else if (src) emitParagraph('Iframe: ' + src); + for (var c of Array.from(doc.body.children)) { + processNode(c); + } + emitTag('IFRAME_CLOSE', id); + inlineWalked = true; } } - - // Body rows: tbody > tr if present; otherwise direct tr children. - // Cap at 200 rows + 30 cols to keep snapshot size sane on huge tables. - var bodyRows = table.querySelectorAll(':scope > tbody > tr'); - if (bodyRows.length === 0) { - bodyRows = table.querySelectorAll(':scope > tr'); + catch (_e) { + // Cross-origin — script can't pierce. Fall through. } - var rowList = Array.from(bodyRows).slice(0, 200); - - // If we don't have explicit headers AND the first row is all , - // promote it to headers and skip it from body rows. - if (!headers && rowList.length > 0) { - var firstRowCells = Array.from(rowList[0].children); - var allTh = firstRowCells.length > 0 && - firstRowCells.every(function(c) { return c.tagName === 'TH'; }); - if (allTh) { - headers = firstRowCells.map(cellText); - rowList = rowList.slice(1); - } + if (!inlineWalked) { + emitTag('IFRAME', id); + if (title) emitParagraph('Cross-origin iframe: ' + title + (src ? ' (' + src + ')' : '')); + else if (src) emitParagraph('Cross-origin iframe: ' + src); } - - var rows = rowList.map(function(tr) { - return Array.from(tr.querySelectorAll(':scope > td, :scope > th')) - .slice(0, 30) - .map(cellText); - }).filter(function(row) { - return row.some(function(c) { return c.length > 0; }); - }); - - if (rows.length === 0 && !headers) return; - var seg = { kind: 'table', rows: rows }; - if (headers) seg.headers = headers; - if (caption) seg.caption = caption; - segments.push(seg); } if (cookieBannerActionId) { @@ -375,6 +349,21 @@ export const EXTRACTOR_SCRIPT = ` // Skip invisible, scripts, styles, etc. if (['script', 'style', 'noscript', 'template', 'svg'].includes(tag)) return; + // Shadow DOM piercing. Modern web components (Salesforce + // Lightning, MS 365, every-Lit-app, custom elements) hide + // their interactive content behind a shadow root. Without + // piercing them the LLM sees an empty with no + // text or actions. We only get OPEN shadow roots — closed + // ones are intentionally inaccessible to scripts (browser + // limitation, not fixable). Process before tag-specific + // handling so shadow content gets an action ID even if the + // host element is also tagged. + if (el.shadowRoot) { + for (const sChild of Array.from(el.shadowRoot.children)) { + processNode(sChild); + } + } + // Headings if (/^h[1-6]$/.test(tag)) { emitHeading(el, parseInt(tag[1], 10)); @@ -387,16 +376,6 @@ export const EXTRACTOR_SCRIPT = ` return; } - // Tables — extract structurally so the LLM sees a real markdown - // table instead of a flat sequence of cell text. This is the - // key win for data-extraction tasks (pricing pages, comparison - // matrices, dashboards). Uses table headers (thead or first row - // of th cells) as column labels when present. - if (tag === 'table') { - emitTable(el); - return; - } - // Interactive: button, link, input, select, textarea if (tag === 'button' || (tag === 'input' && (el.type === 'submit' || el.type === 'button'))) { const label = getAccessibleName(el); @@ -474,6 +453,20 @@ export const EXTRACTOR_SCRIPT = ` return; } + // Iframes. Two strategies: + // 1. Same-origin: walk contentDocument.body inline so the LLM + // sees iframe content as if part of the parent page. + // Many embedded forms / docs / maps fit this case. + // 2. Cross-origin: contentDocument access throws; fall back + // to emitting an IFRAME tag with the src URL so the LLM + // at least knows the iframe is there. Clicking elements + // inside cross-origin iframes requires runner-side + // frame-aware dispatch — a separate change. + if (tag === 'iframe' || tag === 'frame') { + emitIframe(el); + return; + } + // Images if (tag === 'img') { const id = nextId('img'); From bf440f0422f618333ce3ebadd455c9830a6cd76d Mon Sep 17 00:00:00 2001 From: rrader26 Date: Sun, 26 Apr 2026 16:39:50 -0400 Subject: [PATCH 4/7] chore: bump version to 0.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b655a3..d1276b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@thinkfleet/agentmark", - "version": "0.1.0", + "version": "0.2.0", "description": "Reference implementation of the agentmark spec — convert any web page into an AI-friendly Markdown representation. Spec: docs/specs/agentmark-v0.1.md", "type": "commonjs", "main": "./dist/src/index.js", From f254fd37855c50b23364d58261e2aa89a4ca1d21 Mon Sep 17 00:00:00 2001 From: rrader26 Date: Sun, 26 Apr 2026 17:05:59 -0400 Subject: [PATCH 5/7] fix(ci): add typescript devDep + lockfile + restore table support in extractor Three fixes from CI run #24966542292: 1. `typescript` was missing from devDependencies even though the build script runs `tsc`. Added `^5.4.0`. 2. `package-lock.json` didn't exist, breaking `npm ci` in the workflow. Generated. 3. The third commit on this branch (iframes + Shadow DOM) overwrote the table type addition + emitTable helper + processNode branch from the second commit (table support). Re-applied them on top so the BodySegment union, the helpers, and the processNode handler are all in dom-extractor.ts together. Verified locally: `npm run build` is clean, all 90 tests pass (including the 5 new table tests). --- package-lock.json | 846 ++++++++++++++++++++++++++++++++ package.json | 3 +- src/extractors/dom-extractor.ts | 74 +++ 3 files changed, 922 insertions(+), 1 deletion(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8636be0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,846 @@ +{ + "name": "@thinkfleet/agentmark", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@thinkfleet/agentmark", + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "js-yaml": "^4.1.0", + "tslib": "2.6.2" + }, + "devDependencies": { + "@types/js-yaml": "4.0.9", + "@types/node": "20.19.9", + "playwright-core": "^1.40.0", + "typescript": "^5.4.0", + "vitest": "3.0.8" + }, + "peerDependencies": { + "playwright-core": ">=1.40.0" + }, + "peerDependenciesMeta": { + "playwright-core": { + "optional": false + } + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.9", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.0.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.0.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.0.8", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.0.8", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/@vitest/pretty-format": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/postcss": { + "version": "8.5.12", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.0.8", + "@vitest/mocker": "3.0.8", + "@vitest/pretty-format": "^3.0.8", + "@vitest/runner": "3.0.8", + "@vitest/snapshot": "3.0.8", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.1.0", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.8", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.8", + "@vitest/ui": "3.0.8", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json index d1276b5..745e8bd 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "vitest": "3.0.8", "@types/node": "20.19.9", "@types/js-yaml": "4.0.9", - "playwright-core": "^1.40.0" + "playwright-core": "^1.40.0", + "typescript": "^5.4.0" } } diff --git a/src/extractors/dom-extractor.ts b/src/extractors/dom-extractor.ts index 0e6d5e4..cc10c30 100644 --- a/src/extractors/dom-extractor.ts +++ b/src/extractors/dom-extractor.ts @@ -63,6 +63,7 @@ export type BodySegment = | { kind: 'list'; ordered: boolean; items: string[] } | { kind: 'tag'; tag: string; ref?: string } | { kind: 'separator' } + | { kind: 'table'; headers?: string[]; rows: string[][]; caption?: string } /** * The browser-side extractor function, as a string. We pass this to @@ -293,6 +294,69 @@ export const EXTRACTOR_SCRIPT = ` segments.push({ kind: 'tag', tag: tag, ref: ref }); } + function cellText(td) { + // Skip nested actions: cells often contain links/buttons whose + // accessible name is captured separately as actions. Text-only + // cell content is what the LLM needs to read tabular data. + return (td.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 512); + } + + function emitTable(table) { + // Caption: or aria-label. + var caption; + var captionEl = table.querySelector(':scope > caption'); + if (captionEl && captionEl.textContent) { + caption = captionEl.textContent.trim().slice(0, 256); + } else if (table.getAttribute('aria-label')) { + caption = (table.getAttribute('aria-label') || '').slice(0, 256); + } + + // Headers: prefer , fallback to first row if it's + // all , else no headers. + var headers; + var theadRow = table.querySelector(':scope > thead > tr'); + if (theadRow) { + var hCells = Array.from(theadRow.querySelectorAll(':scope > th, :scope > td')); + if (hCells.length > 0) { + headers = hCells.map(cellText); + } + } + + // Body rows: tbody > tr if present; otherwise direct tr children. + // Cap at 200 rows + 30 cols to keep snapshot size sane on huge tables. + var bodyRows = table.querySelectorAll(':scope > tbody > tr'); + if (bodyRows.length === 0) { + bodyRows = table.querySelectorAll(':scope > tr'); + } + var rowList = Array.from(bodyRows).slice(0, 200); + + // If we don't have explicit headers AND the first row is all , + // promote it to headers and skip it from body rows. + if (!headers && rowList.length > 0) { + var firstRowCells = Array.from(rowList[0].children); + var allTh = firstRowCells.length > 0 && + firstRowCells.every(function(c) { return c.tagName === 'TH'; }); + if (allTh) { + headers = firstRowCells.map(cellText); + rowList = rowList.slice(1); + } + } + + var rows = rowList.map(function(tr) { + return Array.from(tr.querySelectorAll(':scope > td, :scope > th')) + .slice(0, 30) + .map(cellText); + }).filter(function(row) { + return row.some(function(c) { return c.length > 0; }); + }); + + if (rows.length === 0 && !headers) return; + var seg = { kind: 'table', rows: rows }; + if (headers) seg.headers = headers; + if (caption) seg.caption = caption; + segments.push(seg); + } + function emitIframe(el) { var src = el.getAttribute('src') || ''; var title = el.getAttribute('title') || el.getAttribute('aria-label') || ''; @@ -467,6 +531,16 @@ export const EXTRACTOR_SCRIPT = ` return; } + // Tables — extract structurally so the LLM sees a real markdown + // table instead of a flat sequence of cell text. This is the + // key win for data-extraction tasks (pricing pages, comparison + // matrices, dashboards). Uses table headers (thead or first row + // of th cells) as column labels when present. + if (tag === 'table') { + emitTable(el); + return; + } + // Images if (tag === 'img') { const id = nextId('img'); From 86225577e46064fe2f590101bf1d00426fcf8812 Mon Sep 17 00:00:00 2001 From: rrader26 Date: Sun, 26 Apr 2026 17:07:58 -0400 Subject: [PATCH 6/7] fix(ci): npm install instead of npm ci for cross-platform compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `npm ci` fails when the lockfile was generated on a different OS than the CI runner — the rollup native bindings (and other platform-specific optional deps) get omitted from the lockfile and then can't be found at install time. Known npm bug: https://github.com/npm/cli/issues/4828 Switch both CI jobs to `npm install --no-audit --no-fund` which fetches platform-appropriate optional deps. Slightly less reproducible but cross-platform CI works again. Until the npm bug is fixed, this is the standard workaround used by vitest, rollup, esbuild, swc, and many other native-bindings projects. --- .github/workflows/ci.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc70bce..7771f48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,12 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - - run: npm ci + # `npm install` instead of `npm ci` — works around npm bug + # https://github.com/npm/cli/issues/4828 where lockfiles + # generated on one OS omit the optional platform-specific + # binaries another OS needs (rollup native bindings, etc). + # Reproducibility is slightly worse but cross-platform CI works. + - run: npm install --no-audit --no-fund - run: npm run build - run: npm test @@ -34,7 +39,12 @@ jobs: with: node-version: 20 registry-url: 'https://registry.npmjs.org' - - run: npm ci + # `npm install` instead of `npm ci` — works around npm bug + # https://github.com/npm/cli/issues/4828 where lockfiles + # generated on one OS omit the optional platform-specific + # binaries another OS needs (rollup native bindings, etc). + # Reproducibility is slightly worse but cross-platform CI works. + - run: npm install --no-audit --no-fund - run: npm run build - run: npm publish --provenance --access public env: From b2b490daae98fe7853b521bff4bc446fbaf8cb33 Mon Sep 17 00:00:00 2001 From: rrader26 Date: Sun, 26 Apr 2026 17:09:24 -0400 Subject: [PATCH 7/7] chore: don't commit package-lock.json (cross-platform npm bug workaround) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standard practice for publishable npm libraries: no committed lockfile, downstream consumers resolve their own deps. Doubly necessary here because of the npm cross-platform optional-deps bug (https://github.com/npm/cli/issues/4828) — lockfiles generated on one OS omit the native binding packages other OSes need (rollup, esbuild, swc), breaking CI when the lockfile + runner OS mismatch. Adding lockfiles to .gitignore alongside node_modules and dist. --- .gitignore | 14 +- package-lock.json | 846 ---------------------------------------------- 2 files changed, 13 insertions(+), 847 deletions(-) delete mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index 62ccde4..42637cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,16 @@ +# npm — we don't commit a lockfile for this library because: +# 1. We publish to npm; downstream consumers resolve their own deps +# 2. Cross-platform optional native deps (rollup, esbuild, swc bindings) +# get omitted from the lockfile when generated on one OS, breaking +# install on others — known npm bug: +# https://github.com/npm/cli/issues/4828 node_modules/ +package-lock.json +yarn.lock +pnpm-lock.yaml + dist/ -*.tsbuildinfo .DS_Store +*.log +.env +.env.local diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 8636be0..0000000 --- a/package-lock.json +++ /dev/null @@ -1,846 +0,0 @@ -{ - "name": "@thinkfleet/agentmark", - "version": "0.2.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@thinkfleet/agentmark", - "version": "0.2.0", - "license": "MIT", - "dependencies": { - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "js-yaml": "^4.1.0", - "tslib": "2.6.2" - }, - "devDependencies": { - "@types/js-yaml": "4.0.9", - "@types/node": "20.19.9", - "playwright-core": "^1.40.0", - "typescript": "^5.4.0", - "vitest": "3.0.8" - }, - "peerDependencies": { - "playwright-core": ">=1.40.0" - }, - "peerDependenciesMeta": { - "playwright-core": { - "optional": false - } - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.2", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/js-yaml": { - "version": "4.0.9", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.9", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@vitest/expect": { - "version": "3.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.0.8", - "@vitest/utils": "3.0.8", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "3.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.0.8", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "3.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "3.0.8", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "3.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.0.8", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { - "version": "3.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "3.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^3.0.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "3.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.0.8", - "loupe": "^3.1.3", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils/node_modules/@vitest/pretty-format": { - "version": "3.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "license": "Python-2.0" - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/chai": { - "version": "5.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/check-error": { - "version": "2.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.25.12", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fdir": { - "version": "6.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/loupe": { - "version": "3.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/playwright-core": { - "version": "1.59.1", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/postcss": { - "version": "8.5.12", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.60.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.2", - "@rollup/rollup-android-arm64": "4.60.2", - "@rollup/rollup-darwin-arm64": "4.60.2", - "@rollup/rollup-darwin-x64": "4.60.2", - "@rollup/rollup-freebsd-arm64": "4.60.2", - "@rollup/rollup-freebsd-x64": "4.60.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", - "@rollup/rollup-linux-arm-musleabihf": "4.60.2", - "@rollup/rollup-linux-arm64-gnu": "4.60.2", - "@rollup/rollup-linux-arm64-musl": "4.60.2", - "@rollup/rollup-linux-loong64-gnu": "4.60.2", - "@rollup/rollup-linux-loong64-musl": "4.60.2", - "@rollup/rollup-linux-ppc64-gnu": "4.60.2", - "@rollup/rollup-linux-ppc64-musl": "4.60.2", - "@rollup/rollup-linux-riscv64-gnu": "4.60.2", - "@rollup/rollup-linux-riscv64-musl": "4.60.2", - "@rollup/rollup-linux-s390x-gnu": "4.60.2", - "@rollup/rollup-linux-x64-gnu": "4.60.2", - "@rollup/rollup-linux-x64-musl": "4.60.2", - "@rollup/rollup-openbsd-x64": "4.60.2", - "@rollup/rollup-openharmony-arm64": "4.60.2", - "@rollup/rollup-win32-arm64-msvc": "4.60.2", - "@rollup/rollup-win32-ia32-msvc": "4.60.2", - "@rollup/rollup-win32-x64-gnu": "4.60.2", - "@rollup/rollup-win32-x64-msvc": "4.60.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.10.0", - "dev": true, - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinypool": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/tinyrainbow": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "license": "0BSD" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "6.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "3.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.0", - "es-module-lexer": "^1.6.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest": { - "version": "3.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "3.0.8", - "@vitest/mocker": "3.0.8", - "@vitest/pretty-format": "^3.0.8", - "@vitest/runner": "3.0.8", - "@vitest/snapshot": "3.0.8", - "@vitest/spy": "3.0.8", - "@vitest/utils": "3.0.8", - "chai": "^5.2.0", - "debug": "^4.4.0", - "expect-type": "^1.1.0", - "magic-string": "^0.30.17", - "pathe": "^2.0.3", - "std-env": "^3.8.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinypool": "^1.0.2", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.8", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.0.8", - "@vitest/ui": "3.0.8", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/debug": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - } - } -}