From ec36b9d497ae9c07142a4ee7dad792940363c66c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 01:21:34 +0000 Subject: [PATCH 1/5] bench: add vitest benchmarks for diff, display lines, and TUI rendering Covers computeSimpleDiff worst cases, buildDisplayLines cold/warm cache, and PullRequestDetail keystroke/scroll latency via ink-testing-library. https://claude.ai/code/session_01HQLaGAamsw5NVDERVKR5Yt --- bench/buildDisplayLines.bench.ts | 117 ++++++++++++++++++++++++ bench/computeSimpleDiff.bench.ts | 55 ++++++++++++ bench/detailRender.bench.tsx | 147 +++++++++++++++++++++++++++++++ knip.json | 2 +- 4 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 bench/buildDisplayLines.bench.ts create mode 100644 bench/computeSimpleDiff.bench.ts create mode 100644 bench/detailRender.bench.tsx diff --git a/bench/buildDisplayLines.bench.ts b/bench/buildDisplayLines.bench.ts new file mode 100644 index 0000000..f3d3397 --- /dev/null +++ b/bench/buildDisplayLines.bench.ts @@ -0,0 +1,117 @@ +import type { Difference } from "@aws-sdk/client-codecommit"; +import { bench, describe } from "vitest"; +import type { CommentThread } from "../src/services/codecommit.js"; +import { buildDisplayLines, type DisplayLine } from "../src/utils/displayLines.js"; + +function makeLines(count: number, prefix: string): string { + return Array.from({ length: count }, (_, i) => `${prefix} line ${i} with some content`).join( + "\n", + ); +} + +function makeFixture(fileCount: number, linesPerFile: number) { + const differences: Difference[] = []; + const diffTexts = new Map(); + const diffTextStatus = new Map(); + + for (let f = 0; f < fileCount; f++) { + const beforeBlobId = `before-${f}`; + const afterBlobId = `after-${f}`; + differences.push({ + beforeBlob: { blobId: beforeBlobId, path: `src/file-${f}.ts` }, + afterBlob: { blobId: afterBlobId, path: `src/file-${f}.ts` }, + changeType: "M", + }); + const key = `${beforeBlobId}:${afterBlobId}`; + const before = makeLines(linesPerFile, `f${f}-old`); + const after = makeLines(linesPerFile, `f${f}-new`); + diffTexts.set(key, { before, after }); + diffTextStatus.set(key, "loaded"); + } + + return { differences, diffTexts, diffTextStatus }; +} + +function makeInlineThreads(fileCount: number, threadsPerFile: number): CommentThread[] { + const threads: CommentThread[] = []; + for (let f = 0; f < fileCount; f++) { + for (let t = 0; t < threadsPerFile; t++) { + threads.push({ + location: { + filePath: `src/file-${f}.ts`, + filePosition: t * 10 + 1, + relativeFileVersion: "AFTER", + }, + comments: [ + { + commentId: `c-${f}-${t}`, + content: `comment ${t} on file ${f}`, + authorArn: "arn:aws:iam::123456789012:user/reviewer", + }, + ], + }); + } + } + return threads; +} + +const small = makeFixture(5, 100); +const large = makeFixture(20, 400); +const withComments = makeFixture(10, 200); +const inlineThreads = makeInlineThreads(10, 5); + +describe("buildDisplayLines", () => { + bench("5 files x 100 lines, no comments, cold cache", () => { + buildDisplayLines( + small.differences, + small.diffTexts, + small.diffTextStatus, + new Map(), + [], + new Map(), + new Map(), + new Map(), + ); + }); + + bench("20 files x 400 lines, no comments, cold cache", () => { + buildDisplayLines( + large.differences, + large.diffTexts, + large.diffTextStatus, + new Map(), + [], + new Map(), + new Map(), + new Map(), + ); + }); + + const warmCache = new Map(); + bench("20 files x 400 lines, no comments, warm cache", () => { + buildDisplayLines( + large.differences, + large.diffTexts, + large.diffTextStatus, + new Map(), + [], + new Map(), + new Map(), + warmCache, + ); + }); + + const warmCacheComments = new Map(); + bench("10 files x 200 lines, 50 inline threads, warm cache", () => { + buildDisplayLines( + withComments.differences, + withComments.diffTexts, + withComments.diffTextStatus, + new Map(), + inlineThreads, + new Map(), + new Map(), + warmCacheComments, + ); + }); +}); diff --git a/bench/computeSimpleDiff.bench.ts b/bench/computeSimpleDiff.bench.ts new file mode 100644 index 0000000..60df211 --- /dev/null +++ b/bench/computeSimpleDiff.bench.ts @@ -0,0 +1,55 @@ +import { bench, describe } from "vitest"; +import { computeSimpleDiff } from "../src/utils/formatDiff.js"; + +function makeLines(count: number, prefix: string): string[] { + return Array.from({ length: count }, (_, i) => `${prefix} line ${i} with some content`); +} + +// Identical files: pure context path +const identical2k = makeLines(2000, "same"); + +// Fully rewritten file: every before-line is deleted, every after-line is added. +// Worst case for unbounded lookahead scans. +const rewrittenBefore2k = makeLines(2000, "old"); +const rewrittenAfter2k = makeLines(2000, "new"); + +// Realistic edit: large file with several small change blocks +const editedBefore = makeLines(3000, "ctx"); +const editedAfter = (() => { + const lines = [...editedBefore]; + for (let block = 0; block < 10; block++) { + const at = 250 + block * 250; + lines.splice(at, 5, ...makeLines(8, `edit${block}`)); + } + return lines; +})(); + +// Reordered blocks: triggers the 5-line lookahead match path +const reorderedBefore = makeLines(1000, "blk"); +const reorderedAfter = (() => { + const lines = [...reorderedBefore]; + for (let i = 0; i < lines.length - 3; i += 7) { + const tmp = lines[i]!; + lines[i] = lines[i + 3]!; + lines[i + 3] = tmp; + } + return lines; +})(); + +describe("computeSimpleDiff", () => { + bench("identical 2000-line files", () => { + computeSimpleDiff(identical2k, identical2k); + }); + + bench("fully rewritten 2000-line file", () => { + computeSimpleDiff(rewrittenBefore2k, rewrittenAfter2k); + }); + + bench("3000-line file with 10 edit blocks", () => { + computeSimpleDiff(editedBefore, editedAfter); + }); + + bench("1000-line file with reordered blocks", () => { + computeSimpleDiff(reorderedBefore, reorderedAfter); + }); +}); diff --git a/bench/detailRender.bench.tsx b/bench/detailRender.bench.tsx new file mode 100644 index 0000000..f942905 --- /dev/null +++ b/bench/detailRender.bench.tsx @@ -0,0 +1,147 @@ +import type { Difference } from "@aws-sdk/client-codecommit"; +import { render } from "ink-testing-library"; +import React from "react"; +import { bench, describe } from "vitest"; +import { PullRequestDetail } from "../src/components/PullRequestDetail.js"; + +const noop = () => {}; + +function makeLines(count: number, prefix: string): string { + return Array.from({ length: count }, (_, i) => `${prefix} line ${i} with some content`).join( + "\n", + ); +} + +function makeFixture(fileCount: number, linesPerFile: number) { + const differences: Difference[] = []; + const diffTexts = new Map(); + const diffTextStatus = new Map(); + + for (let f = 0; f < fileCount; f++) { + const beforeBlobId = `before-${f}`; + const afterBlobId = `after-${f}`; + differences.push({ + beforeBlob: { blobId: beforeBlobId, path: `src/file-${f}.ts` }, + afterBlob: { blobId: afterBlobId, path: `src/file-${f}.ts` }, + changeType: "M", + }); + const key = `${beforeBlobId}:${afterBlobId}`; + diffTexts.set(key, { + before: makeLines(linesPerFile, `f${f}-old`), + after: makeLines(linesPerFile, `f${f}-new`), + }); + diffTextStatus.set(key, "loaded"); + } + + return { differences, diffTexts, diffTextStatus }; +} + +const pullRequest = { + pullRequestId: "42", + title: "perf: benchmark fixture", + authorArn: "arn:aws:iam::123456789012:user/watany", + pullRequestStatus: "OPEN", + creationDate: new Date("2026-02-13T10:00:00Z"), + pullRequestTargets: [ + { + destinationReference: "refs/heads/main", + sourceReference: "refs/heads/feature/perf", + }, + ], +}; + +const asyncActionProps = { + onPost: noop, + isProcessing: false, + error: null, + onClearError: noop, +}; + +const fixture = makeFixture(10, 200); + +function renderDetail() { + return render( + + Promise.resolve({ mergeable: true, conflictCount: 0, conflictFiles: [] }), + isProcessing: false, + error: null, + onClearError: noop, + }} + close={{ onClose: noop, isProcessing: false, error: null, onClearError: noop }} + commitView={{ + commits: [], + differences: [], + diffTexts: new Map(), + isLoading: false, + onLoad: noop, + commitsAvailable: false, + }} + editComment={{ onUpdate: noop, isProcessing: false, error: null, onClearError: noop }} + deleteComment={{ onDelete: noop, isProcessing: false, error: null, onClearError: noop }} + reaction={{ + byComment: new Map(), + onReact: noop, + isProcessing: false, + error: null, + onClearError: noop, + }} + />, + ); +} + +const flush = () => new Promise((r) => setTimeout(r, 0)); + +describe("PullRequestDetail rendering (10 files x 200 lines)", () => { + bench("initial mount + unmount", async () => { + const instance = renderDetail(); + await flush(); + instance.unmount(); + }); + + // Alternate down/up so the cursor keeps moving (a clamped cursor skips re-render) + const navInstance = renderDetail(); + let navDown = true; + bench("j/k keystroke (cursor move + re-render)", async () => { + navInstance.stdin.write(navDown ? "j" : "k"); + navDown = !navDown; + await flush(); + }); + + // Unhandled key: measures stdin parsing + flush overhead without a re-render + const noopInstance = renderDetail(); + bench("no-op keystroke (no re-render)", async () => { + noopInstance.stdin.write("z"); + await flush(); + }); + + const pageInstance = renderDetail(); + let pageDown = true; + bench("Ctrl+d/u half-page scroll + re-render", async () => { + pageInstance.stdin.write(pageDown ? "\x04" : "\x15"); + pageDown = !pageDown; + await flush(); + }); +}); diff --git a/knip.json b/knip.json index e96d67f..61cba06 100644 --- a/knip.json +++ b/knip.json @@ -1,4 +1,4 @@ { "$schema": "https://unpkg.com/knip@5/schema.json", - "ignore": ["src/**/*.test.{ts,tsx}"] + "ignore": ["src/**/*.test.{ts,tsx}", "bench/**"] } From 72d44c4f480c0edc62e42dc7c6025c4c7163f801 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 01:21:34 +0000 Subject: [PATCH 2/5] perf: replace unbounded indexOf lookahead with 5-line window in computeSimpleDiff The greedy diff only uses matches within 5 lines, but indexOf scanned to the end of the array, making rewrites O(n^2). Scanning just the window is semantically identical and linear. Benchmark (mean): fully rewritten 2000-line file 44.9ms -> 0.085ms, 3000-line file with 10 edit blocks 77.3ms -> 0.23ms. https://claude.ai/code/session_01HQLaGAamsw5NVDERVKR5Yt --- src/utils/formatDiff.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/utils/formatDiff.ts b/src/utils/formatDiff.ts index 5f0812d..84eda1f 100644 --- a/src/utils/formatDiff.ts +++ b/src/utils/formatDiff.ts @@ -23,6 +23,18 @@ export interface DisplayLine { reactionText?: string; } +/** Lookahead window for detecting nearby matching lines (reorders/small edits). */ +const LOOKAHEAD_WINDOW = 5; + +/** Returns true if `target` appears in `lines` within the lookahead window starting at `start`. */ +function hasNearbyMatch(lines: string[], start: number, target: string): boolean { + const end = Math.min(lines.length, start + LOOKAHEAD_WINDOW); + for (let i = start; i < end; i++) { + if (lines[i] === target) return true; + } + return false; +} + /** * Computes a simplified line-by-line diff between two sets of lines. * @@ -68,9 +80,8 @@ export function computeSimpleDiff(beforeLines: string[], afterLines: string[]): (ai >= afterLines.length || beforeLines[bi] !== afterLines[ai]) ) { const bl = beforeLines[bi]!; - // Optimization: look ahead to see if this line appears soon in 'after' - const nextMatch = afterLines.indexOf(bl, ai); - if (nextMatch !== -1 && nextMatch - ai < 5) break; // Stop if match found within 5 lines + // Optimization: stop if this line appears within the lookahead window in 'after' + if (hasNearbyMatch(afterLines, ai, bl)) break; result.push({ type: "delete", text: `-${bl}`, @@ -85,9 +96,8 @@ export function computeSimpleDiff(beforeLines: string[], afterLines: string[]): (bi >= beforeLines.length || afterLines[ai] !== beforeLines[bi]) ) { const al = afterLines[ai]!; - // Optimization: look ahead to see if this line appears soon in 'before' - const nextMatch = beforeLines.indexOf(al, bi); - if (nextMatch !== -1 && nextMatch - bi < 5) break; // Stop if match found within 5 lines + // Optimization: stop if this line appears within the lookahead window in 'before' + if (hasNearbyMatch(beforeLines, bi, al)) break; result.push({ type: "add", text: `+${al}`, From fa80cd3d5ea8020e34c99db54c00c2f8b96b2e05 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 01:21:49 +0000 Subject: [PATCH 3/5] perf: cut per-render allocations in display lines and memoize diff rows - Enrich cached diff lines with filePath/diffKey once instead of spreading every line on every buildDisplayLines call - Skip inline-thread matching entirely when no inline comments exist - Count lines without split("\n") on the warm-cache path and in the t-key handler (countLines helper) - Hoist the separator string constant - Render rows through a memoized DiffRow with a single top-level Text; stable line identity from the cache lets unchanged rows skip reconciliation on cursor moves Benchmark (mean): buildDisplayLines 20 files x 400 lines warm cache 3.8ms -> 0.42ms, cold cache 81.9ms -> 1.6ms. https://claude.ai/code/session_01HQLaGAamsw5NVDERVKR5Yt --- src/components/DiffLine.tsx | 23 +++++++++++++++++- src/components/PullRequestDetail.tsx | 13 ++++------- src/utils/displayLines.ts | 35 +++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/components/DiffLine.tsx b/src/components/DiffLine.tsx index cd5b5cb..b3a0e5b 100644 --- a/src/components/DiffLine.tsx +++ b/src/components/DiffLine.tsx @@ -2,6 +2,27 @@ import { Text } from "ink"; import React from "react"; import type { DisplayLine } from "../utils/formatDiff.js"; +/** + * Memoized row: display lines have stable identity (cached in buildDisplayLines), + * so rows re-render only when the cursor enters or leaves them. + * A single top-level Text (nested Texts render inline) keeps the layout + * tree at one node per row instead of a Box plus two Texts. + */ +export const DiffRow = React.memo(function DiffRow({ + line, + isCursor, +}: { + line: DisplayLine; + isCursor: boolean; +}) { + return ( + + {isCursor ? "> " : " "} + {renderDiffLine(line, isCursor)} + + ); +}); + function formatGutter(line: DisplayLine): string { const before = line.beforeLineNumber !== undefined ? String(line.beforeLineNumber).padStart(4) : " "; @@ -10,7 +31,7 @@ function formatGutter(line: DisplayLine): string { return `${before} ${after} │ `; } -export function renderDiffLine(line: DisplayLine, isCursor = false): React.ReactNode { +function renderDiffLine(line: DisplayLine, isCursor = false): React.ReactNode { const bold = isCursor; switch (line.type) { case "header": diff --git a/src/components/PullRequestDetail.tsx b/src/components/PullRequestDetail.tsx index 50fe590..8ad3877 100644 --- a/src/components/PullRequestDetail.tsx +++ b/src/components/PullRequestDetail.tsx @@ -12,6 +12,7 @@ import type { import { buildDisplayLines, COMMENT_LINE_TYPES, + countLines, DIFF_CHUNK_SIZE, type DisplayLine, FOLD_THRESHOLD, @@ -22,7 +23,7 @@ import { extractAuthorName, formatRelativeDate } from "../utils/formatDate.js"; import { CommentInput } from "./CommentInput.js"; import { ConfirmPrompt } from "./ConfirmPrompt.js"; import { ConflictDisplay } from "./ConflictDisplay.js"; -import { renderDiffLine } from "./DiffLine.js"; +import { DiffRow } from "./DiffLine.js"; import { MergeStrategySelector } from "./MergeStrategySelector.js"; import { ReactionPicker } from "./ReactionPicker.js"; @@ -488,7 +489,7 @@ export function PullRequestDetail({ const texts = diffTexts.get(diffKey); /* v8 ignore next -- diffKey originates from diffTexts entries */ if (!texts) return; - const totalLines = texts.before.split("\n").length + texts.after.split("\n").length; + const totalLines = countLines(texts.before) + countLines(texts.after); if (totalLines <= LARGE_DIFF_THRESHOLD) return; const currentLimit = diffLineLimits.get(diffKey) ?? DIFF_CHUNK_SIZE; /* v8 ignore next -- requires many t-presses to reach full expansion */ @@ -728,13 +729,7 @@ export function PullRequestDetail({ {visibleLines.map((line, index) => { const globalIndex = scrollOffset + index; - const isCursor = globalIndex === cursorIndex; - return ( - - {isCursor ? "> " : " "} - {renderDiffLine(line, isCursor)} - - ); + return ; })} {isCommenting && ( diff --git a/src/utils/displayLines.ts b/src/utils/displayLines.ts index c6fdea9..f39e36f 100644 --- a/src/utils/displayLines.ts +++ b/src/utils/displayLines.ts @@ -17,6 +17,19 @@ export const COMMENT_LINE_TYPES = new Set([ export const FOLD_THRESHOLD = 4; +const SEPARATOR_TEXT = "─".repeat(50); + +/** Counts lines in `text` without allocating an array (equivalent to split("\n").length). */ +export function countLines(text: string): number { + let count = 1; + let idx = text.indexOf("\n"); + while (idx !== -1) { + count++; + idx = text.indexOf("\n", idx + 1); + } + return count; +} + export function getThreadKey(thread: CommentThread, index: number): string { const rootComment = thread.comments.find((comment) => !comment.inReplyTo) ?? thread.comments[0]; return rootComment?.commentId ?? `thread-${index}`; @@ -183,22 +196,25 @@ export function buildDisplayLines( for (const diff of differences) { const filePath = diff.afterBlob?.path ?? diff.beforeBlob?.path ?? "(unknown file)"; lines.push({ type: "header", text: filePath }); - lines.push({ type: "separator", text: "─".repeat(50) }); + lines.push({ type: "separator", text: SEPARATOR_TEXT }); const blobKey = `${diff.beforeBlob?.blobId ?? ""}:${diff.afterBlob?.blobId ?? ""}`; const texts = diffTexts.get(blobKey); const status = diffTextStatus.get(blobKey) ?? "loading"; if (texts) { - const beforeLines = texts.before.split("\n"); - const afterLines = texts.after.split("\n"); - const totalLines = beforeLines.length + afterLines.length; + const beforeCount = countLines(texts.before); + const afterCount = countLines(texts.after); + const totalLines = beforeCount + afterCount; const defaultLimit = totalLines > LARGE_DIFF_THRESHOLD ? DIFF_CHUNK_SIZE : totalLines; const currentLimit = diffLineLimits.get(blobKey) ?? defaultLimit; const displayLimit = Math.min(currentLimit, totalLines); const cacheKey = `${blobKey}:${displayLimit}`; let diffLines = diffCache?.get(cacheKey); if (!diffLines) { + // Split only on cache miss; the warm path never needs the line arrays + const beforeLines = texts.before.split("\n"); + const afterLines = texts.after.split("\n"); const { beforeLimit, afterLimit } = getSliceLimits( beforeLines.length, afterLines.length, @@ -208,11 +224,18 @@ export function buildDisplayLines( beforeLines.slice(0, beforeLimit), afterLines.slice(0, afterLimit), ); + // Enrich once here so the hot loop below can push lines without per-call copies + for (const dl of diffLines) { + dl.filePath = filePath; + dl.diffKey = blobKey; + } diffCache?.set(cacheKey, diffLines); } + const hasInlineThreads = inlineThreadsByKey.size > 0; for (const dl of diffLines) { - lines.push({ ...dl, filePath, diffKey: blobKey }); + lines.push(dl); + if (!hasInlineThreads) continue; const matchingEntries = findMatchingThreadEntries(inlineThreadsByKey, filePath, dl); for (const { thread, index: threadIdx } of matchingEntries) { appendThreadLines( @@ -269,7 +292,7 @@ export function buildDisplayLines( (sum, { thread }) => sum + thread.comments.length, 0, ); - lines.push({ type: "separator", text: "─".repeat(50) }); + lines.push({ type: "separator", text: SEPARATOR_TEXT }); lines.push({ type: "comment-header", text: `Comments (${totalComments}):` }); for (const { thread, index: threadIdx } of generalThreads) { appendThreadLines( From c5d3c09f471c578a98553725679418fa106168f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 01:21:50 +0000 Subject: [PATCH 4/5] perf: patch ink/string-width text measurement and ship production React build string-width@8.1.1 runs grapheme segmentation plus an RGI emoji regex per cluster even for plain ASCII; profiling showed ~64% of keystroke render time there. Patches: - string-width: fast path returning length for printable-ASCII strings - ink render-node-to-output: reuse the memoized measureText cache instead of re-running widestLine on every frame - ink output: cache per-character widths (glyphs repeat across frames) build.ts now inlines NODE_ENV=production so the bundled CLI uses the production react-reconciler instead of deciding at runtime (users rarely set NODE_ENV); bundle shrinks 1224.6KB -> 1087.4KB. Keystroke render in PullRequestDetail (10 files x 200 lines): 33.8ms -> 4.2ms per keystroke. https://claude.ai/code/session_01HQLaGAamsw5NVDERVKR5Yt --- build.ts | 4 +++ patches/ink@6.7.0.patch | 55 ++++++++++++++++++++++++++++++++ patches/string-width@8.1.1.patch | 17 ++++++++++ 3 files changed, 76 insertions(+) create mode 100644 patches/ink@6.7.0.patch create mode 100644 patches/string-width@8.1.1.patch diff --git a/build.ts b/build.ts index 5ff83cc..5b0ba25 100644 --- a/build.ts +++ b/build.ts @@ -21,6 +21,9 @@ const [cliResult, libResult] = await Promise.all([ minify: true, naming: "cli.mjs", plugins: [stubDevtools], + // Inline NODE_ENV so react-reconciler/react bundle their production + // builds instead of deciding at runtime (users rarely set NODE_ENV) + define: { "process.env.NODE_ENV": '"production"' }, }), Bun.build({ entrypoints: ["./src/index.ts"], @@ -29,6 +32,7 @@ const [cliResult, libResult] = await Promise.all([ target: "node", minify: true, plugins: [stubDevtools], + define: { "process.env.NODE_ENV": '"production"' }, }), ]); diff --git a/patches/ink@6.7.0.patch b/patches/ink@6.7.0.patch new file mode 100644 index 0000000..ff579fd --- /dev/null +++ b/patches/ink@6.7.0.patch @@ -0,0 +1,55 @@ +diff --git a/node_modules/ink/.bun-tag-d5e25726408cb9c1 b/.bun-tag-d5e25726408cb9c1 +new file mode 100644 +index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +diff --git a/build/output.js b/build/output.js +index 59d2c970a51245212ab72def70b556fea95386e4..33d6f3c05fa8eebbad64b34f51a2845293a123f1 100644 +--- a/build/output.js ++++ b/build/output.js +@@ -1,5 +1,17 @@ + import sliceAnsi from 'slice-ansi'; + import stringWidth from 'string-width'; ++ ++// Width cache for single styled characters; glyphs repeat heavily across ++// frames (ASCII, box-drawing), so this avoids re-segmenting per character. ++const characterWidthCache = new Map(); ++const cachedCharacterWidth = (value) => { ++ let width = characterWidthCache.get(value); ++ if (width === undefined) { ++ width = Math.max(1, stringWidth(value)); ++ characterWidthCache.set(value, width); ++ } ++ return width; ++}; + import widestLine from 'widest-line'; + import { styledCharsFromTokens, styledCharsToString, tokenize, } from '@alcalzone/ansi-tokenize'; + export default class Output { +@@ -116,7 +128,7 @@ export default class Output { + for (const character of characters) { + currentLine[offsetX] = character; + // Determine printed width using string-width to align with measurement +- const characterWidth = Math.max(1, stringWidth(character.value)); ++ const characterWidth = cachedCharacterWidth(character.value); + // For multi-column characters, clear following cells to avoid stray spaces/artifacts + if (characterWidth > 1) { + for (let index = 1; index < characterWidth; index++) { +diff --git a/build/render-node-to-output.js b/build/render-node-to-output.js +index f00a27842045de18edfc7fbd6c65467a1d6f641c..5d2acc0045a47c87bb2fe70824a3a5008cf186e5 100644 +--- a/build/render-node-to-output.js ++++ b/build/render-node-to-output.js +@@ -1,4 +1,5 @@ + import widestLine from 'widest-line'; ++import measureText from './measure-text.js'; + import indentString from 'indent-string'; + import Yoga from 'yoga-layout'; + import wrapText from './wrap-text.js'; +@@ -90,7 +91,9 @@ const renderNodeToOutput = (node, output, options) => { + if (node.nodeName === 'ink-text') { + let text = squashTextNodes(node); + if (text.length > 0) { +- const currentWidth = widestLine(text); ++ // Use the memoized text measurement to avoid re-segmenting ++ // unchanged text on every frame ++ const currentWidth = measureText(text).width; + const maxWidth = getMaxWidth(yogaNode); + if (currentWidth > maxWidth) { + const textWrap = node.style.textWrap ?? 'wrap'; diff --git a/patches/string-width@8.1.1.patch b/patches/string-width@8.1.1.patch new file mode 100644 index 0000000..6e7815f --- /dev/null +++ b/patches/string-width@8.1.1.patch @@ -0,0 +1,17 @@ +diff --git a/index.js b/index.js +index 1140bf61114ff9ca08610c22345af16a2bfc1f53..df74f1832b8210bc0a8dce4498131b0e600fc79b 100644 +--- a/index.js ++++ b/index.js +@@ -62,6 +62,12 @@ export default function stringWidth(input, options = {}) { + return 0; + } + ++ // Fast path: printable ASCII strings are one column per character, ++ // skipping grapheme segmentation and the RGI emoji regex entirely. ++ if (!/[^\x20-\x7E]/.test(string)) { ++ return string.length; ++ } ++ + let width = 0; + const eastAsianWidthOptions = {ambiguousAsWide: !ambiguousIsNarrow}; + From 4fe0bdd66cc179de26a97305135c16cd4200930f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 01:21:56 +0000 Subject: [PATCH 5/5] chore: register dependency patches, add bench script, fix audit findings - Register string-width/ink patchedDependencies and add bun run bench - Update overrides (fast-xml-parser, fast-xml-builder, ws, postcss, esbuild) and bump vitest to ^4.1.0 to clear bun audit advisories - Allow esbuild in minimumReleaseAgeExcludes (fix is younger than 7 days) https://claude.ai/code/session_01HQLaGAamsw5NVDERVKR5Yt --- bun.lock | 124 ++++++++++++++++++++++----------------------------- bunfig.toml | 2 +- package.json | 17 +++++-- 3 files changed, 68 insertions(+), 75 deletions(-) diff --git a/bun.lock b/bun.lock index 5ca3985..e2b1362 100644 --- a/bun.lock +++ b/bun.lock @@ -18,16 +18,24 @@ "oxlint": "^0.15.0", "react": "19.2.4", "typescript": "^5.7.0", - "vitest": "^4.0.18", + "vitest": "^4.1.0", }, }, }, + "patchedDependencies": { + "ink@6.7.0": "patches/ink@6.7.0.patch", + "string-width@8.1.1": "patches/string-width@8.1.1.patch", + }, "overrides": { - "fast-xml-parser": ">=5.5.7", + "esbuild": ">=0.28.1", + "fast-xml-builder": ">=1.1.7", + "fast-xml-parser": ">=5.7.0", "picomatch": ">=4.0.4", + "postcss": ">=8.5.10", "rollup": ">=4.59.0", "smol-toml": ">=1.6.1", "vite": ">=7.3.2 <8.0.0", + "ws": ">=8.20.1", }, "packages": { "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], @@ -124,58 +132,6 @@ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-nGsF/4C7uzUj+Nj/4J+Zt0bYQ6bz33Phz8Lb2N80Mti1HjGclTJdXZ+9APC4kLvONbjxN1zfvYNd8FEcbBK/MQ=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.5", "", { "os": "android", "cpu": "arm" }, "sha512-Cv781jd0Rfj/paoNrul1/r4G0HLvuFKYh7C9uHZ2Pl8YXstzvCyyeWENTFR9qFnRzNMCjXmsulZuvosDg10Mog=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.5", "", { "os": "android", "cpu": "arm64" }, "sha512-Oeghq+XFgh1pUGd1YKs4DDoxzxkoUkvko+T/IVKwlghKLvvjbGFB3ek8VEDBmNvqhwuL0CQS3cExdzpmUyIrgA=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.5", "", { "os": "android", "cpu": "x64" }, "sha512-nQD7lspbzerlmtNOxYMFAGmhxgzn8Z7m9jgFkh6kpkjsAhZee1w8tJW3ZlW+N9iRePz0oPUDrYrXidCPSImD0Q=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-I+Ya/MgC6rr8oRWGRDF3BXDfP8K1BVUggHqN6VI2lUZLdDi1IM1v2cy0e3lCPbP+pVcK3Tv8cgUhHse1kaNZZw=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-MCjQUtC8wWJn/pIPM7vQaO69BFgwPD1jriEdqwTCKzWjGgkMbcg+M5HzrOhPhuYe1AJjXlHmD142KQf+jnYj8A=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-X6xVS+goSH0UelYXnuf4GHLwpOdc8rgK/zai+dKzBMnncw7BTQIwquOodE7EKvY2UVUetSqyAfyZC1D+oqLQtg=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-233X1FGo3a8x1ekLB6XT69LfZ83vqz+9z3TSEQCTYfMNY880A97nr81KbPcAMl9rmOFp11wO0dP+eB18KU/Ucg=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.5", "", { "os": "linux", "cpu": "arm" }, "sha512-0wkVrYHG4sdCCN/bcwQ7yYMXACkaHc3UFeaEOwSVW6e5RycMageYAFv+JS2bKLwHyeKVUvtoVH+5/RHq0fgeFw=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-euKkilsNOv7x/M1NKsx5znyprbpsRFIzTV6lWziqJch7yWYayfLtZzDxDTl+LSQDJYAjd9TVb/Kt5UKIrj2e4A=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-hVRQX4+P3MS36NxOy24v/Cdsimy/5HYePw+tmPqnNN1fxV0bPrFWR6TMqwXPwoTM2VzbkA+4lbHWUKDd5ZDA/w=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.5", "", { "os": "linux", "cpu": "none" }, "sha512-mKqqRuOPALI8nDzhOBmIS0INvZOOFGGg5n1osGIXAx8oersceEbKd4t1ACNTHM3sJBXGFAlEgqM+svzjPot+ZQ=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.5", "", { "os": "linux", "cpu": "none" }, "sha512-EE/QXH9IyaAj1qeuIV5+/GZkBTipgGO782Ff7Um3vPS9cvLhJJeATy4Ggxikz2inZ46KByamMn6GqtqyVjhenA=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-0V2iF1RGxBf1b7/BjurA5jfkl7PtySjom1r6xOK2q9KWw/XCpAdtB6KNMO+9xx69yYfSCRR9FE0TyKfHA2eQMw=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.5", "", { "os": "linux", "cpu": "none" }, "sha512-rYxThBx6G9HN6tFNuvB/vykeLi4VDsm5hE5pVwzqbAjZEARQrWu3noZSfbEnPZ/CRXP3271GyFk/49up2W190g=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-uEP2q/4qgd8goEUc4QIdU/1P2NmEtZ/zX5u3OpLlCGhJIuBIv0s0wr7TB2nBrd3/A5XIdEkkS5ZLF0ULuvaaYQ=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.5", "", { "os": "linux", "cpu": "x64" }, "sha512-+Gq47Wqq6PLOOZuBzVSII2//9yyHNKZLuwfzCemqexqOQCSz0zy0O26kIzyp9EMNMK+nZ0tFHBZrCeVUuMs/ew=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.5", "", { "os": "none", "cpu": "arm64" }, "sha512-3F/5EG8VHfN/I+W5cO1/SV2H9Q/5r7vcHabMnBqhHK2lTWOh3F8vixNzo8lqxrlmBtZVFpW8pmITHnq54+Tq4g=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.5", "", { "os": "none", "cpu": "x64" }, "sha512-28t+Sj3CPN8vkMOlZotOmDgilQwVvxWZl7b8rxpn73Tt/gCnvrHxQUMng4uu3itdFvrtba/1nHejvxqz8xgEMA=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.5", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Doz/hKtiuVAi9hMsBMpwBANhIZc8l238U2Onko3t2xUp8xtM0ZKdDYHMnm/qPFVthY8KtxkXaocwmMh6VolzMA=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-WfGVaa1oz5A7+ZFPkERIbIhKT4olvGl1tyzTRaB5yoZRLqC0KwaO95FeZtOdQj/oKkjW57KcVF944m62/0GYtA=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.5", "", { "os": "none", "cpu": "arm64" }, "sha512-Xh+VRuh6OMh3uJ0JkCjI57l+DVe7VRGBYymen8rFPnTVgATBwA6nmToxM2OwTlSvrnWpPKkrQUj93+K9huYC6A=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-aC1gpJkkaUADHuAdQfuVTnqVUTLqqUNhAvEwHwVWcnVVZvNlDPGA0UveZsfXJJ9T6k9Po4eHi3c02gbdwO3g6w=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-0UNx2aavV0fk6UpZcwXFLztA2r/k9jTUa7OW7SAea1VYUhkug99MW1uZeXEnPn5+cHOd0n8myQay6TlFnBR07w=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-5nlJ3AeJWCTSzR7AEqVjT/faWyqKU86kCi1lLmxVqmNR+j4HrYdns+eTGjS/vmrzCIe8inGQckUadvS0+JkKdQ=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.5", "", { "os": "win32", "cpu": "x64" }, "sha512-PWypQR+d4FLfkhBIV+/kHsUELAnMpx1bRvvsn3p+/sAERbnCzFrtDRG2Xw5n+2zPxBK2+iaP+vetsRl4Ti7WgA=="], - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], @@ -184,6 +140,8 @@ "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + "@nodable/entities": ["@nodable/entities@2.1.1", "", {}, "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg=="], + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], @@ -394,17 +352,17 @@ "@vitest/coverage-v8": ["@vitest/coverage-v8@4.0.18", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.0.18", "ast-v8-to-istanbul": "^0.3.10", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.1", "obug": "^2.1.1", "std-env": "^3.10.0", "tinyrainbow": "^3.0.3" }, "peerDependencies": { "@vitest/browser": "4.0.18", "vitest": "4.0.18" }, "optionalPeers": ["@vitest/browser"] }, "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg=="], - "@vitest/expect": ["@vitest/expect@4.0.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ=="], + "@vitest/expect": ["@vitest/expect@4.1.8", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.8", "@vitest/utils": "4.1.8", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ=="], - "@vitest/mocker": ["@vitest/mocker@4.0.18", "", { "dependencies": { "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ=="], + "@vitest/mocker": ["@vitest/mocker@4.1.8", "", { "dependencies": { "@vitest/spy": "4.1.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw=="], - "@vitest/pretty-format": ["@vitest/pretty-format@4.0.18", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw=="], + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.8", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA=="], - "@vitest/runner": ["@vitest/runner@4.0.18", "", { "dependencies": { "@vitest/utils": "4.0.18", "pathe": "^2.0.3" } }, "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw=="], + "@vitest/runner": ["@vitest/runner@4.1.8", "", { "dependencies": { "@vitest/utils": "4.1.8", "pathe": "^2.0.3" } }, "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg=="], - "@vitest/snapshot": ["@vitest/snapshot@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA=="], + "@vitest/snapshot": ["@vitest/snapshot@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "@vitest/utils": "4.1.8", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ=="], - "@vitest/spy": ["@vitest/spy@4.0.18", "", {}, "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw=="], + "@vitest/spy": ["@vitest/spy@4.1.8", "", {}, "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA=="], "@vitest/utils": ["@vitest/utils@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "tinyrainbow": "^3.0.3" } }, "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA=="], @@ -440,6 +398,8 @@ "code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], @@ -448,11 +408,11 @@ "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], - "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], "es-toolkit": ["es-toolkit@1.44.0", "", {}, "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg=="], - "esbuild": ["esbuild@0.27.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.5", "@esbuild/android-arm": "0.27.5", "@esbuild/android-arm64": "0.27.5", "@esbuild/android-x64": "0.27.5", "@esbuild/darwin-arm64": "0.27.5", "@esbuild/darwin-x64": "0.27.5", "@esbuild/freebsd-arm64": "0.27.5", "@esbuild/freebsd-x64": "0.27.5", "@esbuild/linux-arm": "0.27.5", "@esbuild/linux-arm64": "0.27.5", "@esbuild/linux-ia32": "0.27.5", "@esbuild/linux-loong64": "0.27.5", "@esbuild/linux-mips64el": "0.27.5", "@esbuild/linux-ppc64": "0.27.5", "@esbuild/linux-riscv64": "0.27.5", "@esbuild/linux-s390x": "0.27.5", "@esbuild/linux-x64": "0.27.5", "@esbuild/netbsd-arm64": "0.27.5", "@esbuild/netbsd-x64": "0.27.5", "@esbuild/openbsd-arm64": "0.27.5", "@esbuild/openbsd-x64": "0.27.5", "@esbuild/openharmony-arm64": "0.27.5", "@esbuild/sunos-x64": "0.27.5", "@esbuild/win32-arm64": "0.27.5", "@esbuild/win32-ia32": "0.27.5", "@esbuild/win32-x64": "0.27.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-zdQoHBjuDqKsvV5OPaWansOwfSQ0Js+Uj9J85TBvj3bFW1JjWTSULMRwdQAc8qMeIScbClxeMK0jlrtB9linhA=="], + "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], "escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], @@ -464,9 +424,9 @@ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - "fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="], + "fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="], - "fast-xml-parser": ["fast-xml-parser@5.5.9", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g=="], + "fast-xml-parser": ["fast-xml-parser@5.8.0", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.2.0", "path-expression-matcher": "^1.5.0", "strnum": "^2.3.0", "xml-naming": "^0.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg=="], "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], @@ -534,7 +494,7 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], @@ -546,7 +506,7 @@ "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], - "path-expression-matcher": ["path-expression-matcher@1.2.0", "", {}, "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ=="], + "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], @@ -554,7 +514,7 @@ "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], - "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], @@ -598,7 +558,7 @@ "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], - "strnum": ["strnum@2.2.2", "", {}, "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA=="], + "strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -626,7 +586,7 @@ "vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="], - "vitest": ["vitest@4.0.18", "", { "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", "@vitest/pretty-format": "4.0.18", "@vitest/runner": "4.0.18", "@vitest/snapshot": "4.0.18", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^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": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ=="], + "vitest": ["vitest@4.1.8", "", { "dependencies": { "@vitest/expect": "4.1.8", "@vitest/mocker": "4.1.8", "@vitest/pretty-format": "4.1.8", "@vitest/runner": "4.1.8", "@vitest/snapshot": "4.1.8", "@vitest/spy": "4.1.8", "@vitest/utils": "4.1.8", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^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.1.8", "@vitest/browser-preview": "4.1.8", "@vitest/browser-webdriverio": "4.1.8", "@vitest/coverage-istanbul": "4.1.8", "@vitest/coverage-v8": "4.1.8", "@vitest/ui": "4.1.8", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig=="], "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], @@ -636,7 +596,9 @@ "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], - "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="], "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], @@ -646,14 +608,36 @@ "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@vitest/expect/@vitest/utils": ["@vitest/utils@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg=="], + + "@vitest/expect/tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "@vitest/pretty-format/tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "@vitest/runner/@vitest/utils": ["@vitest/utils@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg=="], + + "@vitest/snapshot/@vitest/utils": ["@vitest/utils@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg=="], + + "@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@4.0.18", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw=="], + "ink-text-input/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "vitest/@vitest/utils": ["@vitest/utils@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg=="], + + "vitest/std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], + + "vitest/tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@vitest/runner/@vitest/utils/tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "@vitest/snapshot/@vitest/utils/tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], diff --git a/bunfig.toml b/bunfig.toml index d02fe67..32b597c 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,3 +1,3 @@ [install] minimumReleaseAge = 604800 -minimumReleaseAgeExcludes = ["vite", "picomatch", "smol-toml"] +minimumReleaseAgeExcludes = ["vite", "picomatch", "smol-toml", "esbuild"] diff --git a/package.json b/package.json index 8ada515..2f53b5b 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "scripts": { "build": "bun run build.ts && tsc --emitDeclarationOnly && chmod +x dist/cli.mjs", "test": "vitest run", + "bench": "vitest bench --run", "test:coverage": "vitest run --coverage", "test:watch": "vitest", "lint": "oxlint -c .oxlintrc.json --tsconfig ./tsconfig.json --import-plugin --deny-warnings ./src", @@ -48,7 +49,7 @@ "oxlint": "^0.15.0", "react": "19.2.4", "typescript": "^5.7.0", - "vitest": "^4.0.18" + "vitest": "^4.1.0" }, "repository": { "type": "git", @@ -73,11 +74,19 @@ }, "license": "MIT", "overrides": { - "fast-xml-parser": ">=5.5.7", + "fast-xml-parser": ">=5.7.0", + "fast-xml-builder": ">=1.1.7", "rollup": ">=4.59.0", "smol-toml": ">=1.6.1", "vite": ">=7.3.2 <8.0.0", - "picomatch": ">=4.0.4" + "picomatch": ">=4.0.4", + "ws": ">=8.20.1", + "postcss": ">=8.5.10", + "esbuild": ">=0.28.1" }, - "dependencies": {} + "dependencies": {}, + "patchedDependencies": { + "string-width@8.1.1": "patches/string-width@8.1.1.patch", + "ink@6.7.0": "patches/ink@6.7.0.patch" + } }