diff --git a/.changeset/trim-cli-dependencies.md b/.changeset/trim-cli-dependencies.md new file mode 100644 index 000000000..4a81418e1 --- /dev/null +++ b/.changeset/trim-cli-dependencies.md @@ -0,0 +1,5 @@ +--- +"react-doctor": patch +--- + +Reduce the CLI dependency graph by replacing narrow code-frame and terminal-symbol helpers with local implementations. diff --git a/packages/evals/package.json b/packages/evals/package.json index 0f8125075..945a92c60 100644 --- a/packages/evals/package.json +++ b/packages/evals/package.json @@ -14,8 +14,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@daytona/sdk": "^0.196.0", - "p-limit": "^3.1.0" + "@daytona/sdk": "^0.196.0" }, "devDependencies": { "@types/node": "^25.6.0", diff --git a/packages/evals/src/cleanup-evaluation-sandboxes.ts b/packages/evals/src/cleanup-evaluation-sandboxes.ts index 8ac13fafe..1b59562d0 100644 --- a/packages/evals/src/cleanup-evaluation-sandboxes.ts +++ b/packages/evals/src/cleanup-evaluation-sandboxes.ts @@ -1,10 +1,9 @@ import { DaytonaNotFoundError, SandboxState } from "@daytona/sdk"; import type { Daytona, Sandbox } from "@daytona/sdk"; -import pLimit from "p-limit"; - import { SANDBOX_CLEANUP_CONCURRENCY, SANDBOX_DELETE_TIMEOUT_SECONDS } from "./constants.js"; import { toErrorMessage } from "./utils/to-error-message.js"; import { runBeforeDeadline } from "./utils/run-before-deadline.js"; +import { createConcurrencyLimit } from "./utils/create-concurrency-limit.js"; export interface CleanupEvaluationSandboxesInput { daytona: Daytona; @@ -17,7 +16,7 @@ export const cleanupEvaluationSandboxes = async ({ evaluationId, deadlineMilliseconds, }: CleanupEvaluationSandboxesInput): Promise => { - const cleanupLimit = pLimit(SANDBOX_CLEANUP_CONCURRENCY); + const cleanupLimit = createConcurrencyLimit(SANDBOX_CLEANUP_CONCURRENCY); const remainingSandboxes = await runBeforeDeadline({ operation: async () => { const sandboxes: Sandbox[] = []; diff --git a/packages/evals/src/run-corpus-evaluation.ts b/packages/evals/src/run-corpus-evaluation.ts index 6dfdb2ca1..b5ac7843b 100644 --- a/packages/evals/src/run-corpus-evaluation.ts +++ b/packages/evals/src/run-corpus-evaluation.ts @@ -2,7 +2,6 @@ import { randomUUID } from "node:crypto"; import { open } from "node:fs/promises"; import { Daytona, DaytonaNotFoundError, Image } from "@daytona/sdk"; -import pLimit from "p-limit"; import { cleanupEvaluationSandboxes } from "./cleanup-evaluation-sandboxes.js"; import { deleteDaytonaSnapshotBeforeDeadline } from "./utils/delete-daytona-snapshot-before-deadline.js"; @@ -44,6 +43,7 @@ import type { EvaluationOptions } from "./parse-evaluation-arguments.js"; import { runEvaluationAttempts } from "./run-evaluation-attempts.js"; import { runMatrixCorpusEvaluation } from "./run-matrix-corpus-evaluation.js"; import { createPairedNdjsonWriter } from "./utils/create-paired-ndjson-writer.js"; +import { createConcurrencyLimit } from "./utils/create-concurrency-limit.js"; import { getEvaluationAttemptDeadlineMilliseconds } from "./utils/get-evaluation-attempt-deadline-milliseconds.js"; import { getEvaluatorSourceHash } from "./utils/get-evaluator-source-hash.js"; import { getEvaluationTimeoutSeconds } from "./utils/get-evaluation-timeout-seconds.js"; @@ -196,7 +196,7 @@ export const runCorpusEvaluation = async (options: EvaluationOptions): Promise diff --git a/packages/evals/src/run-evaluation-attempts.ts b/packages/evals/src/run-evaluation-attempts.ts index 980c8e506..1796607b4 100644 --- a/packages/evals/src/run-evaluation-attempts.ts +++ b/packages/evals/src/run-evaluation-attempts.ts @@ -1,9 +1,8 @@ -import pLimit from "p-limit"; - import { EVALUATION_RETRY_REPOSITORIES_PER_SANDBOX } from "./constants.js"; import type { CorpusEvaluationRecord, CorpusRepositoryGroup } from "./corpus.js"; import { groupCorpusRepositories } from "./group-corpus-repositories.js"; import { partitionRepositoryGroups } from "./utils/partition-repository-groups.js"; +import { createConcurrencyLimit } from "./utils/create-concurrency-limit.js"; export interface EvaluationRetry { attemptNumber: number; @@ -38,7 +37,7 @@ export const runEvaluationAttempts = async ({ }: RunEvaluationAttemptsInput): Promise => { let pendingRepositoryGroups = repositoryGroups; for (const [attemptIndex, concurrency] of attemptConcurrencies.entries()) { - const limit = pLimit(concurrency); + const limit = createConcurrencyLimit(concurrency); const repositoryBatchSize = attemptIndex === 0 ? repositoriesPerSandbox : EVALUATION_RETRY_REPOSITORIES_PER_SANDBOX; const repositoryBatches = partitionRepositoryGroups( diff --git a/packages/evals/src/run-matrix-corpus-evaluation.ts b/packages/evals/src/run-matrix-corpus-evaluation.ts index 1e6f5f08f..bb4090e86 100644 --- a/packages/evals/src/run-matrix-corpus-evaluation.ts +++ b/packages/evals/src/run-matrix-corpus-evaluation.ts @@ -5,7 +5,6 @@ import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; import { Daytona, DaytonaNotFoundError, Image } from "@daytona/sdk"; -import pLimit from "p-limit"; import { buildMatrixEvaluationPlan } from "./build-matrix-evaluation-plan.js"; import type { MatrixEvaluationLane } from "./build-matrix-evaluation-plan.js"; @@ -37,6 +36,7 @@ import { runMatrixEvaluationAttempts } from "./run-matrix-evaluation-attempts.js import { abortWriters } from "./utils/abort-writers.js"; import { assertMatrixBaseRecord } from "./utils/assert-matrix-base-record.js"; import { createMatrixBaseArtifactBinding } from "./utils/matrix-base-artifact-binding.js"; +import { createConcurrencyLimit } from "./utils/create-concurrency-limit.js"; import type { MatrixBaseArtifactBinding } from "./utils/matrix-base-artifact-binding.js"; import { deleteDaytonaSnapshotBeforeDeadline } from "./utils/delete-daytona-snapshot-before-deadline.js"; import { getEvaluationAttemptDeadlineMilliseconds } from "./utils/get-evaluation-attempt-deadline-milliseconds.js"; @@ -266,7 +266,9 @@ export const runMatrixCorpusEvaluation = async (options: EvaluationOptions): Pro Math.min(options.concurrency, concurrency), ), ]; - const limitSandboxCreation = pLimit(Math.min(options.concurrency, SANDBOX_CREATE_CONCURRENCY)); + const limitSandboxCreation = createConcurrencyLimit( + Math.min(options.concurrency, SANDBOX_CREATE_CONCURRENCY), + ); const createSandbox = (sandboxName: string, deadlineMilliseconds: number) => limitSandboxCreation(() => daytona.create( diff --git a/packages/evals/src/run-matrix-evaluation-attempts.ts b/packages/evals/src/run-matrix-evaluation-attempts.ts index 43b2e53dc..0a120265d 100644 --- a/packages/evals/src/run-matrix-evaluation-attempts.ts +++ b/packages/evals/src/run-matrix-evaluation-attempts.ts @@ -1,10 +1,9 @@ -import pLimit from "p-limit"; - import type { CorpusRepository, CorpusRepositoryGroup } from "./corpus.js"; import type { MatrixEvaluationLane } from "./build-matrix-evaluation-plan.js"; import type { MatrixEvaluationFailure } from "./evaluate-matrix-repository-batch.js"; import { groupCorpusRepositories } from "./group-corpus-repositories.js"; import { partitionRepositoryGroups } from "./utils/partition-repository-groups.js"; +import { createConcurrencyLimit } from "./utils/create-concurrency-limit.js"; import { toErrorMessage } from "./utils/to-error-message.js"; export interface RunMatrixEvaluationAttemptsInput { @@ -136,7 +135,7 @@ export const runMatrixEvaluationAttempts = async ({ ).map((repositoryBatch) => ({ repositoryGroups: repositoryBatch, lanes })); for (const [attemptIndex, concurrency] of attemptConcurrencies.entries()) { - const limit = pLimit(concurrency); + const limit = createConcurrencyLimit(concurrency); const workResults = await Promise.allSettled( pendingWork.map((work) => limit(() => evaluateRepositoryBatch(work.repositoryGroups, work.lanes, attemptIndex)), diff --git a/packages/evals/src/utils/create-concurrency-limit.ts b/packages/evals/src/utils/create-concurrency-limit.ts new file mode 100644 index 000000000..a2888e37d --- /dev/null +++ b/packages/evals/src/utils/create-concurrency-limit.ts @@ -0,0 +1,35 @@ +interface ConcurrencyLimit { + (operation: () => Result | PromiseLike): Promise; +} + +export const createConcurrencyLimit = (concurrency: number): ConcurrencyLimit => { + if (!Number.isInteger(concurrency) || concurrency < 1) { + throw new TypeError("Concurrency must be a positive integer"); + } + + const pendingOperations: Array<() => void> = []; + let activeOperationCount = 0; + + const startNextOperations = (): void => { + while (activeOperationCount < concurrency) { + const startOperation = pendingOperations.shift(); + if (!startOperation) return; + activeOperationCount += 1; + startOperation(); + } + }; + + return (operation: () => Result | PromiseLike): Promise => + new Promise((resolve, reject) => { + pendingOperations.push(() => { + Promise.resolve() + .then(operation) + .then(resolve, reject) + .finally(() => { + activeOperationCount -= 1; + startNextOperations(); + }); + }); + startNextOperations(); + }); +}; diff --git a/packages/evals/src/utils/create-paired-ndjson-writer.ts b/packages/evals/src/utils/create-paired-ndjson-writer.ts index 0cf207b5b..7c8921cf6 100644 --- a/packages/evals/src/utils/create-paired-ndjson-writer.ts +++ b/packages/evals/src/utils/create-paired-ndjson-writer.ts @@ -1,7 +1,6 @@ import type { Writable } from "node:stream"; -import pLimit from "p-limit"; - +import { createConcurrencyLimit } from "./create-concurrency-limit.js"; import { serializeNdjsonRecord } from "./serialize-ndjson-record.js"; import { writeWritableContents } from "./write-writable-contents.js"; @@ -51,7 +50,7 @@ export const createPairedNdjsonWriter = ({ baselineFileHandle, treatmentOutput, }: CreatePairedNdjsonWriterInput): PairedNdjsonWriter => { - const limitWrite = pLimit(1); + const limitWrite = createConcurrencyLimit(1); let baselineOffset = 0; let hasWriteFailed = false; let writeFailure: unknown; diff --git a/packages/evals/tests/create-concurrency-limit.test.ts b/packages/evals/tests/create-concurrency-limit.test.ts new file mode 100644 index 000000000..77f8198d0 --- /dev/null +++ b/packages/evals/tests/create-concurrency-limit.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { createConcurrencyLimit } from "../src/utils/create-concurrency-limit.js"; + +describe("createConcurrencyLimit", () => { + it("runs queued operations in FIFO order without exceeding the limit", async () => { + const limit = createConcurrencyLimit(2); + const startedOperations: number[] = []; + let activeOperationCount = 0; + let maximumActiveOperationCount = 0; + + const results = [1, 2, 3, 4].map((operationId) => + limit(async () => { + startedOperations.push(operationId); + activeOperationCount += 1; + maximumActiveOperationCount = Math.max(maximumActiveOperationCount, activeOperationCount); + await Promise.resolve(); + activeOperationCount -= 1; + return operationId; + }), + ); + + await expect(Promise.all(results)).resolves.toEqual([1, 2, 3, 4]); + expect(startedOperations).toEqual([1, 2, 3, 4]); + expect(maximumActiveOperationCount).toBe(2); + }); + + it("releases a slot when an operation rejects", async () => { + const limit = createConcurrencyLimit(1); + const rejectedOperation = limit(() => Promise.reject(new Error("failed"))); + const nextOperation = limit(() => "completed"); + + await expect(rejectedOperation).rejects.toThrow("failed"); + await expect(nextOperation).resolves.toBe("completed"); + }); + + it("rejects invalid concurrency", () => { + expect(() => createConcurrencyLimit(0)).toThrow("positive integer"); + }); +}); diff --git a/packages/react-doctor/package.json b/packages/react-doctor/package.json index 50cb86a5b..0ed3c056a 100644 --- a/packages/react-doctor/package.json +++ b/packages/react-doctor/package.json @@ -57,14 +57,12 @@ }, "dependencies": { "@astrojs/compiler": "^4.0.0", - "@babel/code-frame": "^7.29.0", "@sentry/node": "^10.54.0", "agent-install": "0.0.5", "conf": "^15.1.0", "confbox": "^0.2.4", "deslop-js": "workspace:*", "eslint-plugin-react-hooks": "^7.1.1", - "figures": "^6.1.0", "jiti": "^2.7.0", "magicast": "^0.5.3", "oxc-resolver": "^11.24.2", diff --git a/packages/react-doctor/src/cli/ink/components/action-menu.tsx b/packages/react-doctor/src/cli/ink/components/action-menu.tsx index 106dff14b..b13268337 100644 --- a/packages/react-doctor/src/cli/ink/components/action-menu.tsx +++ b/packages/react-doctor/src/cli/ink/components/action-menu.tsx @@ -1,10 +1,10 @@ -import figures from "figures"; import { Box, Text, useInput } from "ink"; import type { ReactNode } from "react"; import { TUI_REPORT_ACTION_MENU_ITEM_GAP_ROWS, TUI_REPORT_ACTION_MENU_MARGIN_ROWS, } from "../../utils/constants.js"; +import { terminalSymbols } from "../../utils/terminal-symbols.js"; import { useState } from "../react-runtime.js"; export interface ActionMenuAction { @@ -59,7 +59,7 @@ export const ActionMenu = ({ } > - {isSelected ? figures.pointer : figures.pointerSmall} {action.label} + {isSelected ? terminalSymbols.pointer : terminalSymbols.pointerSmall} {action.label} {isSelected ? action.description : null} diff --git a/packages/react-doctor/src/cli/ink/components/project-select.tsx b/packages/react-doctor/src/cli/ink/components/project-select.tsx index a2f6520e1..4673f834f 100644 --- a/packages/react-doctor/src/cli/ink/components/project-select.tsx +++ b/packages/react-doctor/src/cli/ink/components/project-select.tsx @@ -1,5 +1,4 @@ import path from "node:path"; -import figures from "figures"; import { Box, Text, useInput } from "ink"; import type { ReactNode } from "react"; import type { WorkspacePackage } from "@react-doctor/core"; @@ -15,6 +14,7 @@ import { clampNumber } from "../../utils/clamp-number.js"; import { isPrintableInput } from "../../utils/is-printable-input.js"; import { recordCount } from "../../utils/record-metric.js"; import { resolveVisibleStart } from "../../utils/resolve-visible-start.js"; +import { terminalSymbols } from "../../utils/terminal-symbols.js"; import { useExitOnCtrlC } from "../hooks/use-exit-on-ctrl-c.js"; import { useStdoutDimensions } from "../hooks/use-stdout-dimensions.js"; import { fuzzyMatch } from "../lib/fuzzy-match.js"; @@ -269,10 +269,10 @@ export const ProjectSelect = ({ packages, rootDirectory, onSubmit }: ProjectSele return ( - {isSelected ? `${figures.pointer} ` : " "} + {isSelected ? `${terminalSymbols.pointer} ` : " "} - {isChecked ? `${figures.radioOn} ` : `${figures.radioOff} `} + {isChecked ? `${terminalSymbols.radioOn} ` : `${terminalSymbols.radioOff} `} { return null; } - // A single huge line (minified output, a giant inline data literal) - // only renders an unreadable wall of text, so skip the frame and let - // the caller fall back to the bare `file:line` reference. - const offendingLine = source.split("\n", input.line)[input.line - 1] ?? ""; - if (offendingLine.length > CODE_FRAME_MAX_LINE_LENGTH_CHARS) return null; - - // A spanning frame marks every line in the range and has no single - // caret column; a single-site frame points the caret at the column. - const isRange = input.endLine != null && input.endLine > input.line; - const location = isRange - ? { start: { line: input.line }, end: { line: input.endLine! } } - : { start: { line: input.line, column: input.column > 0 ? input.column : undefined } }; - - return codeFrameColumns(source, location, { - highlightCode: true, + const endLine = input.endLine != null && input.endLine > input.line ? input.endLine : undefined; + return renderCodeFrame({ + source, + line: input.line, + column: endLine === undefined && input.column > 0 ? input.column : undefined, + endLine, + message: input.message, linesAbove: CODE_FRAME_LINES_ABOVE, linesBelow: CODE_FRAME_LINES_BELOW, - ...(input.message ? { message: input.message } : {}), + maximumLineLength: CODE_FRAME_MAX_LINE_LENGTH_CHARS, }); }; diff --git a/packages/react-doctor/src/cli/utils/highlight-code-line.ts b/packages/react-doctor/src/cli/utils/highlight-code-line.ts new file mode 100644 index 000000000..bc61acd38 --- /dev/null +++ b/packages/react-doctor/src/cli/utils/highlight-code-line.ts @@ -0,0 +1,14 @@ +import { highlighter } from "@react-doctor/core"; + +const CODE_TOKEN_PATTERN = + /\/\/.*|\/\*.*?\*\/|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`|\b(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|type|typeof|undefined|var|void|while|with|yield)\b|\b(?:0[xob][\da-f]+|\d+(?:\.\d+)?)\b/giu; + +export const highlightCodeLine = (codeLine: string): string => + codeLine.replace(CODE_TOKEN_PATTERN, (token) => { + if (token.startsWith("/")) return highlighter.gray(token); + if (token.startsWith('"') || token.startsWith("'") || token.startsWith("`")) { + return highlighter.success(token); + } + if (/^\d|^0[xob]/i.test(token)) return highlighter.warn(token); + return highlighter.info(token); + }); diff --git a/packages/react-doctor/src/cli/utils/render-code-frame.ts b/packages/react-doctor/src/cli/utils/render-code-frame.ts new file mode 100644 index 000000000..962605809 --- /dev/null +++ b/packages/react-doctor/src/cli/utils/render-code-frame.ts @@ -0,0 +1,67 @@ +import { highlighter } from "@react-doctor/core"; +import { highlightCodeLine } from "./highlight-code-line.js"; + +interface RenderCodeFrameInput { + readonly source: string; + readonly line: number; + readonly column?: number; + readonly endLine?: number; + readonly message?: string; + readonly linesAbove: number; + readonly linesBelow: number; + readonly maximumLineLength: number; +} + +const SOURCE_LINE_BREAK_PATTERN = /\r\n|[\n\r\u2028\u2029]/; + +export const renderCodeFrame = ({ + source, + line, + column, + endLine, + message, + linesAbove, + linesBelow, + maximumLineLength, +}: RenderCodeFrameInput): string | null => { + const sourceLines = source.split(SOURCE_LINE_BREAK_PATTERN); + if (line < 1 || line > sourceLines.length) return null; + const offendingLine = sourceLines[line - 1]; + if (offendingLine === undefined || offendingLine.length > maximumLineLength) return null; + + const lastMarkedLine = Math.min(Math.max(endLine ?? line, line), sourceLines.length); + const firstDisplayedLine = Math.max(line - linesAbove, 1); + const lastDisplayedLine = Math.min(lastMarkedLine + linesBelow, sourceLines.length); + const lineNumberWidth = String(lastDisplayedLine).length; + const hasCaret = lastMarkedLine === line && column !== undefined; + const outputLines: string[] = []; + + if (message && !hasCaret) { + outputLines.push(`${" ".repeat(lineNumberWidth + 2)}${highlighter.error(message)}`); + } + + for ( + let displayedLineNumber = firstDisplayedLine; + displayedLineNumber <= lastDisplayedLine; + displayedLineNumber += 1 + ) { + const sourceLine = sourceLines[displayedLineNumber - 1] ?? ""; + const isMarked = displayedLineNumber >= line && displayedLineNumber <= lastMarkedLine; + const gutter = ` ${String(displayedLineNumber).padStart(lineNumberWidth)} |`; + const marker = isMarked ? highlighter.error(">") : " "; + outputLines.push( + `${marker}${highlighter.dim(gutter)}${sourceLine.length > 0 ? ` ${highlightCodeLine(sourceLine)}` : ""}`, + ); + + if (isMarked && hasCaret) { + const markerSpacing = sourceLine.slice(0, Math.max(column - 1, 0)).replace(/[^\t]/g, " "); + const emptyGutter = gutter.replace(/\d/g, " "); + const markerMessage = message ? ` ${highlighter.error(message)}` : ""; + outputLines.push( + ` ${highlighter.dim(emptyGutter)} ${markerSpacing}${highlighter.error("^")}${markerMessage}`, + ); + } + } + + return outputLines.join("\n"); +}; diff --git a/packages/react-doctor/src/cli/utils/terminal-symbols.ts b/packages/react-doctor/src/cli/utils/terminal-symbols.ts new file mode 100644 index 000000000..45cbed0e9 --- /dev/null +++ b/packages/react-doctor/src/cli/utils/terminal-symbols.ts @@ -0,0 +1,20 @@ +const supportsUnicodeSymbols = + process.platform !== "win32" + ? process.env.TERM !== "linux" + : Boolean(process.env.WT_SESSION) || + Boolean(process.env.TERMINUS_SUBLIME) || + process.env.ConEmuTask === "{cmd::Cmder}" || + process.env.TERM_PROGRAM === "Terminus-Sublime" || + process.env.TERM_PROGRAM === "vscode" || + process.env.TERM === "xterm-256color" || + process.env.TERM === "alacritty" || + process.env.TERM === "rxvt-unicode" || + process.env.TERM === "rxvt-unicode-256color" || + process.env.TERMINAL_EMULATOR === "JetBrains-JediTerm"; + +export const terminalSymbols = { + pointer: supportsUnicodeSymbols ? "❯" : ">", + pointerSmall: "›", + radioOn: supportsUnicodeSymbols ? "◉" : "(*)", + radioOff: supportsUnicodeSymbols ? "◯" : "( )", +}; diff --git a/packages/react-doctor/tests/build-code-frame.test.ts b/packages/react-doctor/tests/build-code-frame.test.ts index c289abd88..1968c5401 100644 --- a/packages/react-doctor/tests/build-code-frame.test.ts +++ b/packages/react-doctor/tests/build-code-frame.test.ts @@ -1,6 +1,7 @@ import * as fs from "node:fs"; import os from "node:os"; import * as path from "node:path"; +import { stripVTControlCharacters } from "node:util"; import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test"; import { CODE_FRAME_MAX_LINE_LENGTH_CHARS } from "@react-doctor/core"; import { buildCodeFrame } from "../src/cli/utils/build-code-frame.js"; @@ -31,7 +32,41 @@ describe("buildCodeFrame", () => { rootDirectory: temporaryDirectory, }); expect(frame).not.toBeNull(); - expect(frame).toContain("eval(value)"); + const plainFrame = stripVTControlCharacters(frame ?? ""); + expect(plainFrame).toContain("eval(value)"); + expect(plainFrame).toContain(" | ^"); + }); + + it("renders a labeled multi-line range with bounded context", () => { + const filePath = writeFile( + "range.tsx", + ["const before = 1;", "const first = 2;", "const second = 3;", "const after = 4;"].join("\n"), + ); + const frame = buildCodeFrame({ + filePath, + line: 2, + column: 1, + endLine: 3, + message: "Repeated issue", + rootDirectory: temporaryDirectory, + }); + expect(frame).not.toBeNull(); + expect(stripVTControlCharacters(frame ?? "")).toBe( + [ + " Repeated issue", + " 1 | const before = 1;", + "> 2 | const first = 2;", + "> 3 | const second = 3;", + " 4 | const after = 4;", + ].join("\n"), + ); + }); + + it("returns null when the location is past the end of the file", () => { + const filePath = writeFile("short.tsx", "const value = 1;\n"); + expect( + buildCodeFrame({ filePath, line: 5, column: 1, rootDirectory: temporaryDirectory }), + ).toBeNull(); }); it("returns null when the offending line is too long to render usefully", () => { diff --git a/packages/react-doctor/tests/ink/scan-app.test.tsx b/packages/react-doctor/tests/ink/scan-app.test.tsx index 8285d0276..55f8d030f 100644 --- a/packages/react-doctor/tests/ink/scan-app.test.tsx +++ b/packages/react-doctor/tests/ink/scan-app.test.tsx @@ -2,7 +2,6 @@ import { render } from "ink-testing-library"; import { afterEach, describe, expect, it, vi } from "vite-plus/test"; import { GITHUB_ACTIONS_SETUP_URL } from "@react-doctor/core"; import type { Diagnostic, ScoreResult } from "@react-doctor/core"; -import figures from "figures"; import { TUI_DEFAULT_TERMINAL_COLUMNS, TUI_REPORT_COMPACT_MAX_ROWS, @@ -10,6 +9,7 @@ import { TUI_REPORT_STATUS_ROWS, TUI_REPORT_VIEWPORT_MARGIN_ROWS, } from "../../src/cli/utils/constants.js"; +import { terminalSymbols } from "../../src/cli/utils/terminal-symbols.js"; import * as exitGracefullyModule from "../../src/cli/utils/exit-gracefully.js"; import * as launchAgent from "../../src/cli/utils/launch-agent.js"; import * as openUrlModule from "../../src/cli/utils/open-url.js"; @@ -137,7 +137,7 @@ describe("ScanApp", () => { expect(frame).toContain("demo-app"); expect(frame).toContain("React Doctor"); expect(frame).toContain("┌─────┐"); - expect(frame).toContain(`${figures.pointer} Review 2 issues`); + expect(frame).toContain(`${terminalSymbols.pointer} Review 2 issues`); expect(frame).not.toContain("Top 1 error to review first"); expect(frame).not.toContain("Why Your users briefly see stale state on every prop"); const frameLines = frame.split("\n"); @@ -474,7 +474,7 @@ describe("ScanApp", () => { await flush(); resizeTerminal(stdout, { rows: 30 }); await flush(); - expect(lastFrame()).toContain(`${figures.pointer} Review 2 issues`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Review 2 issues`); expect(lastFrame()).toContain("Add to GitHub Actions (Recommended)"); expect(lastFrame()).toContain("58"); stdin.write("\r"); @@ -515,7 +515,7 @@ describe("ScanApp", () => { resizeTerminal(stdout, { rows: 30 }); await flush(); - expect(lastFrame()).toContain(`${figures.pointer} Review 2 issues`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Review 2 issues`); stdin.write("\r"); await flush(); @@ -530,7 +530,7 @@ describe("ScanApp", () => { await flush(); const returnedLandingFrame = lastFrame() ?? ""; expect(returnedLandingFrame).toContain( - `${figures.pointer} Add to GitHub Actions (Recommended)`, + `${terminalSymbols.pointer} Add to GitHub Actions (Recommended)`, ); expect(returnedLandingFrame).toContain("› Review 2 issues"); expect(returnedLandingFrame).not.toContain("Top 1 error to review first"); @@ -574,7 +574,7 @@ describe("ScanApp", () => { stdin.write("\u001B"); await flush(); - expect(lastFrame()).toContain(`${figures.pointer} Hand off to an agent`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Hand off to an agent`); expect(lastFrame()).toContain("› Review 1 issue"); unmount(); }); @@ -865,7 +865,7 @@ describe("ScanApp", () => { await flush(); expect(lastFrame()).toContain("┌─────┐"); - expect(lastFrame()).toContain(`${figures.pointer} Review 30 issues`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Review 30 issues`); stdin.write("\r"); await flush(); @@ -1013,14 +1013,14 @@ describe("ScanApp", () => { ); await flush(); - expect(lastFrame()).toContain(`${figures.pointer} Review 1 issue`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Review 1 issue`); expect(lastFrame()).toContain("Add to GitHub Actions (Recommended)"); expect(lastFrame()).toContain( - `${figures.pointer} Review 1 issue\n\n› Add to GitHub Actions (Recommended)`, + `${terminalSymbols.pointer} Review 1 issue\n\n› Add to GitHub Actions (Recommended)`, ); stdin.write("j"); await flush(); - expect(lastFrame()).toContain(`${figures.pointer} Add to GitHub Actions (Recommended)`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Add to GitHub Actions (Recommended)`); expect(lastFrame()).toContain("Used by teams at PayPal, Rippling, and Alibaba."); stdin.write("\r"); await flush(); @@ -1037,13 +1037,13 @@ describe("ScanApp", () => { ); expect(ciSetupLines[trustLineIndex + 1]).toContain(GITHUB_ACTIONS_SETUP_URL); expect(lastFrame()).not.toContain("`doctor` package script"); - expect(lastFrame()).toContain(`${figures.pointer} Yes, add the workflow`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Yes, add the workflow`); expect(lastFrame()).toContain("Open the GitHub Actions guide"); stdin.write("\u001B"); await flush(); expect(onAddToCi).not.toHaveBeenCalled(); - expect(lastFrame()).toContain(`${figures.pointer} Add to GitHub Actions (Recommended)`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Add to GitHub Actions (Recommended)`); stdin.write("\r"); await flush(); @@ -1149,7 +1149,7 @@ describe("ScanApp", () => { await flush(); stdin.write("j"); await flush(); - expect(lastFrame()).toContain(`${figures.pointer} Hand off to an agent`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Hand off to an agent`); stdin.write("\r"); await flush(); @@ -1158,14 +1158,16 @@ describe("ScanApp", () => { "Scan every pull request to prevent new React issues while you fix the backlog.", ); expect(lastFrame()).toContain("Used by teams at PayPal, Rippling, and Alibaba."); - expect(lastFrame()).toContain(`${figures.pointer} Add to GitHub Actions first (Recommended)`); + expect(lastFrame()).toContain( + `${terminalSymbols.pointer} Add to GitHub Actions first (Recommended)`, + ); expect(lastFrame()).toContain("Continue without GitHub Actions"); stdin.write("\u001B"); await flush(); expect(onAddToCi).not.toHaveBeenCalled(); expect(lastFrame()).toContain(" Choose how to continue"); - expect(lastFrame()).toContain(`${figures.pointer} Codex`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Codex`); expect(lastFrame()).toContain("Cursor"); expect(lastFrame()).toContain("Copy prompt"); @@ -1214,16 +1216,16 @@ describe("ScanApp", () => { stdin.write("\r"); await flush(); expect(onAddToCi).toHaveBeenCalledOnce(); - expect(lastFrame()).toContain(`${figures.pointer} Codex`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Codex`); stdin.write("\u001B"); await flush(); expect(lastFrame()).not.toContain("Add to GitHub Actions (Recommended)"); - expect(lastFrame()).toContain(`${figures.pointer} Hand off to an agent`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Hand off to an agent`); stdin.write("\r"); await flush(); - expect(lastFrame()).toContain(`${figures.pointer} Codex`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Codex`); expect(lastFrame()).not.toContain("Add React Doctor to GitHub Actions first"); stdin.write("\r"); @@ -1262,13 +1264,13 @@ describe("ScanApp", () => { stdin.write("j"); await flush(); - expect(lastFrame()).toContain(`${figures.pointer} Hand off to an agent`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Hand off to an agent`); stdin.write("\r"); await flush(); expect(writeDiagnosticsDirectory).not.toHaveBeenCalled(); expect(lastFrame()).toContain("Choose how to continue"); - expect(lastFrame()).toContain(`${figures.pointer} Copy prompt`); + expect(lastFrame()).toContain(`${terminalSymbols.pointer} Copy prompt`); expect(lastFrame()).toContain("Paste into any agent or edit it first"); stdin.write("\r"); await flush(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 044e59b08..6cb2a6f0a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -119,7 +119,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.0-beta.102 - version: 4.0.0-beta.102(effect@4.0.0-beta.102)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(vite@7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))) + version: 4.0.0-beta.102(effect@4.0.0-beta.102)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(vite@7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))) '@types/node': specifier: ^25.6.0 version: 25.6.0 @@ -189,9 +189,6 @@ importers: '@daytona/sdk': specifier: ^0.196.0 version: 0.196.0(ws@8.21.1) - p-limit: - specifier: ^3.1.0 - version: 3.1.0 devDependencies: '@types/node': specifier: ^25.6.0 @@ -265,9 +262,6 @@ importers: '@astrojs/compiler': specifier: ^4.0.0 version: 4.0.0 - '@babel/code-frame': - specifier: ^7.29.0 - version: 7.29.0 '@sentry/node': specifier: ^10.54.0 version: 10.54.0(@opentelemetry/exporter-trace-otlp-http@0.219.0(@opentelemetry/api@1.9.1)) @@ -286,9 +280,6 @@ importers: eslint-plugin-react-hooks: specifier: ^7.1.1 version: 7.1.1(eslint@9.39.2(jiti@2.7.0)) - figures: - specifier: ^6.1.0 - version: 6.1.0 jiti: specifier: ^2.7.0 version: 2.7.0 @@ -2539,11 +2530,11 @@ packages: resolution: {integrity: sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/expect@4.1.7': - resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==} + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - '@vitest/mocker@4.1.7': - resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==} + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2553,20 +2544,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.7': - resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==} + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} - '@vitest/runner@4.1.7': - resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==} + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - '@vitest/snapshot@4.1.7': - resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==} + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - '@vitest/spy@4.1.7': - resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==} + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} - '@vitest/utils@4.1.7': - resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} '@voidzero-dev/vite-plus-core@0.1.20': resolution: {integrity: sha512-4KmzRfzwTeG3JuvDijrdqWusSgRvLMKDPrVsDdtbDVVjEMq0VnM8lSH+Nvepd6Pg+SuSVUP212OIfH/3Yn1bfA==} @@ -3050,8 +3041,8 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} @@ -3163,8 +3154,8 @@ packages: resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} engines: {node: '>=0.10.0'} - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} extendable-error@0.1.7: @@ -3202,10 +3193,6 @@ packages: picomatch: optional: true - figures@6.1.0: - resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} - engines: {node: '>=18'} - file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -4216,8 +4203,8 @@ packages: resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} engines: {node: ^20.0.0 || >=22.0.0} - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} to-regex-range@5.0.1: @@ -4341,20 +4328,20 @@ packages: yaml: optional: true - vitest@4.1.7: - resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==} + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true 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.7 - '@vitest/browser-preview': 4.1.7 - '@vitest/browser-webdriverio': 4.1.7 - '@vitest/coverage-istanbul': 4.1.7 - '@vitest/coverage-v8': 4.1.7 - '@vitest/ui': 4.1.7 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -5037,10 +5024,10 @@ snapshots: - bufferutil - utf-8-validate - '@effect/vitest@4.0.0-beta.102(effect@4.0.0-beta.102)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(vite@7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)))': + '@effect/vitest@4.0.0-beta.102(effect@4.0.0-beta.102)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(vite@7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)))': dependencies: effect: 4.0.0-beta.102 - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(vite@7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(vite@7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) '@emnapi/core@1.10.0': dependencies: @@ -6394,46 +6381,46 @@ snapshots: '@typescript-eslint/types@8.59.3': {} - '@vitest/expect@4.1.7': + '@vitest/expect@4.1.10': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.7 - '@vitest/utils': 4.1.7 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 chai: 6.2.2 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.7(vite@7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': + '@vitest/mocker@4.1.10(vite@7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.7 + '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) - '@vitest/pretty-format@4.1.7': + '@vitest/pretty-format@4.1.10': dependencies: - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@vitest/runner@4.1.7': + '@vitest/runner@4.1.10': dependencies: - '@vitest/utils': 4.1.7 + '@vitest/utils': 4.1.10 pathe: 2.0.3 - '@vitest/snapshot@4.1.7': + '@vitest/snapshot@4.1.10': dependencies: - '@vitest/pretty-format': 4.1.7 - '@vitest/utils': 4.1.7 + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.7': {} + '@vitest/spy@4.1.10': {} - '@vitest/utils@4.1.7': + '@vitest/utils@4.1.10': dependencies: - '@vitest/pretty-format': 4.1.7 + '@vitest/pretty-format': 4.1.10 convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 '@voidzero-dev/vite-plus-core@0.1.20(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.46.0)(tsx@4.22.4)(typescript@5.9.3)(yaml@2.9.0)': dependencies: @@ -6891,7 +6878,7 @@ snapshots: es-module-lexer@1.7.0: {} - es-module-lexer@2.1.0: {} + es-module-lexer@2.3.1: {} es-object-atoms@1.1.2: dependencies: @@ -7099,7 +7086,7 @@ snapshots: dependencies: homedir-polyfill: 1.0.3 - expect-type@1.3.0: {} + expect-type@1.4.0: {} extendable-error@0.1.7: {} @@ -7131,10 +7118,6 @@ snapshots: optionalDependencies: picomatch: 4.0.4 - figures@6.1.0: - dependencies: - is-unicode-supported: 2.1.0 - file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -8168,7 +8151,7 @@ snapshots: tinypool@2.1.0: {} - tinyrainbow@3.1.0: {} + tinyrainbow@3.1.1: {} to-regex-range@5.0.1: dependencies: @@ -8343,17 +8326,17 @@ snapshots: tsx: 4.22.4 yaml: 2.9.0 - vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(vite@7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.7 - '@vitest/mocker': 4.1.7(vite@7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.7 - '@vitest/runner': 4.1.7 - '@vitest/snapshot': 4.1.7 - '@vitest/spy': 4.1.7 - '@vitest/utils': 4.1.7 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 + vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(vite@7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 magic-string: 0.30.21 obug: 2.1.1 pathe: 2.0.3 @@ -8362,7 +8345,7 @@ snapshots: tinybench: 2.9.0 tinyexec: 1.1.1 tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 vite: 7.3.1(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.46.0)(tsx@4.22.4)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: