From ed3d33c630d8897cc3cee5774b17b4281a53a8d9 Mon Sep 17 00:00:00 2001 From: arlo Date: Tue, 30 Jun 2026 18:47:19 +0800 Subject: [PATCH 1/2] feat(core): mcp --- alias.ts | 1 + packages/core/package.json | 1 + packages/core/src/node/agents.ts | 17 + packages/core/src/node/cli-commands.ts | 9 + packages/core/src/node/cli.ts | 8 + packages/core/src/node/mcp.ts | 133 ++++ packages/core/src/node/standalone.ts | 4 +- packages/rolldown/package.json | 2 +- packages/rolldown/src/node/agent/analysis.ts | 29 + packages/rolldown/src/node/agent/context.ts | 263 ++++++++ packages/rolldown/src/node/agent/index.ts | 11 + .../src/node/agent/modules/build-analysis.ts | 159 +++++ .../node/agent/modules/build-comparison.ts | 117 ++++ .../node/agent/modules/build-time-analysis.ts | 110 ++++ .../agent/modules/bundle-size-analysis.ts | 134 ++++ .../node/agent/modules/dependency-trace.ts | 204 ++++++ packages/rolldown/src/node/agent/tools.ts | 205 ++++++ packages/rolldown/src/node/agent/utils.ts | 38 ++ .../rolldown-get-session-compare-details.ts | 55 +- packages/rolldown/tsdown.config.ts | 5 +- pnpm-lock.yaml | 595 ++++++++++++++++-- pnpm-workspace.yaml | 1 + tsconfig.base.json | 3 + 23 files changed, 2035 insertions(+), 69 deletions(-) create mode 100644 packages/core/src/node/agents.ts create mode 100644 packages/core/src/node/mcp.ts create mode 100644 packages/rolldown/src/node/agent/analysis.ts create mode 100644 packages/rolldown/src/node/agent/context.ts create mode 100644 packages/rolldown/src/node/agent/index.ts create mode 100644 packages/rolldown/src/node/agent/modules/build-analysis.ts create mode 100644 packages/rolldown/src/node/agent/modules/build-comparison.ts create mode 100644 packages/rolldown/src/node/agent/modules/build-time-analysis.ts create mode 100644 packages/rolldown/src/node/agent/modules/bundle-size-analysis.ts create mode 100644 packages/rolldown/src/node/agent/modules/dependency-trace.ts create mode 100644 packages/rolldown/src/node/agent/tools.ts create mode 100644 packages/rolldown/src/node/agent/utils.ts diff --git a/alias.ts b/alias.ts index 3fb58d1ac..644176290 100644 --- a/alias.ts +++ b/alias.ts @@ -15,6 +15,7 @@ export const alias = { '@vitejs/devtools-kit/utils/when': r('kit/src/utils/when.ts'), '@vitejs/devtools-kit/utils/shared-state': r('kit/src/utils/shared-state.ts'), '@vitejs/devtools-kit': r('kit/src/index.ts'), + '@vitejs/devtools-rolldown/node/agent': r('rolldown/src/node/agent/index.ts'), '@vitejs/devtools-rolldown': r('rolldown/src/index.ts'), '@vitejs/devtools-self-inspect': r('self-inspect/src/index.ts'), '@vitejs/devtools/internal': r('core/src/internal.ts'), diff --git a/packages/core/package.json b/packages/core/package.json index 3a34eb1b4..d1b7474a6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -57,6 +57,7 @@ }, "dependencies": { "@devframes/hub": "catalog:deps", + "@modelcontextprotocol/sdk": "catalog:deps", "@vitejs/devtools-kit": "workspace:*", "@vitejs/devtools-rolldown": "workspace:*", "birpc": "catalog:deps", diff --git a/packages/core/src/node/agents.ts b/packages/core/src/node/agents.ts new file mode 100644 index 000000000..f5ee8d12d --- /dev/null +++ b/packages/core/src/node/agents.ts @@ -0,0 +1,17 @@ +import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' +import { rolldownAgent } from '@vitejs/devtools-rolldown/node/agent' + +export interface DevToolsAgent { + namespace: string + setup: (ctx: ViteDevToolsNodeContext) => void | Promise +} + +export const builtinAgents: readonly DevToolsAgent[] = [ + rolldownAgent, +] + +export async function registerBuiltinAgents(ctx: ViteDevToolsNodeContext) { + for (const agent of builtinAgents) { + await agent.setup(ctx) + } +} diff --git a/packages/core/src/node/cli-commands.ts b/packages/core/src/node/cli-commands.ts index 07805d16e..d2e7e92ed 100644 --- a/packages/core/src/node/cli-commands.ts +++ b/packages/core/src/node/cli-commands.ts @@ -91,3 +91,12 @@ export async function build(options: BuildOptions) { diagnostics.DTK0010() } + +export interface McpOptions { + root?: string +} + +export async function mcp(options: McpOptions) { + const { startMcpServer } = await import('./mcp') + await startMcpServer(options) +} diff --git a/packages/core/src/node/cli.ts b/packages/core/src/node/cli.ts index c311619cd..5694ec309 100644 --- a/packages/core/src/node/cli.ts +++ b/packages/core/src/node/cli.ts @@ -20,6 +20,14 @@ cli return await build(options) }) +cli + .command('mcp', 'Start Vite DevTools MCP server over stdio') + .option('--root ', 'Root directory', { default: process.cwd() }) + .action(async (options) => { + const { mcp } = await import('./cli-commands') + return await mcp(options) + }) + cli .command('', 'Start devtools') .option('--root ', 'Root directory', { default: process.cwd() }) diff --git a/packages/core/src/node/mcp.ts b/packages/core/src/node/mcp.ts new file mode 100644 index 000000000..315f000a2 --- /dev/null +++ b/packages/core/src/node/mcp.ts @@ -0,0 +1,133 @@ +/* eslint-disable no-console */ + +import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' +import type { AgentResource, AgentTool, DevframeNodeContext } from 'devframe/types' +import process from 'node:process' +import { createMcpServer } from 'devframe/adapters/mcp' +import { defineDevframe } from 'devframe/types' +import packageJson from '../../package.json' with { type: 'json' } + +export interface McpOptions { + root?: string +} + +function redirectConsoleOutputToStderr() { + const originalLog = console.log + const originalInfo = console.info + const originalDebug = console.debug + console.log = (...args: unknown[]) => { + console.error(...args) + } + console.info = (...args: unknown[]) => { + console.error(...args) + } + console.debug = (...args: unknown[]) => { + console.error(...args) + } + return () => { + console.log = originalLog + console.info = originalInfo + console.debug = originalDebug + } +} + +function waitForStdioClose() { + return new Promise((resolve) => { + const done = () => resolve() + process.stdin.once('end', done) + process.stdin.once('close', done) + process.once('SIGINT', done) + process.once('SIGTERM', done) + }) +} + +function registerToolProxy( + target: DevframeNodeContext, + source: ViteDevToolsNodeContext, + tool: AgentTool, +) { + return target.agent.registerTool({ + id: tool.id, + title: tool.title, + description: tool.description, + safety: tool.safety, + tags: tool.tags, + inputSchema: tool.inputSchema, + outputSchema: tool.outputSchema, + examples: tool.examples, + handler: args => source.agent.invoke(tool.id, args ?? {}), + }) +} + +function registerResourceProxy( + target: DevframeNodeContext, + source: ViteDevToolsNodeContext, + resource: AgentResource, +) { + return target.agent.registerResource({ + id: resource.id, + name: resource.name, + description: resource.description, + mimeType: resource.mimeType, + uri: resource.uri, + read: () => source.agent.read(resource.id), + }) +} + +function mirrorAgentSurface(target: DevframeNodeContext, source: ViteDevToolsNodeContext) { + const handles: Array<{ unregister: () => void }> = [] + + const sync = () => { + while (handles.length) { + handles.pop()!.unregister() + } + + const manifest = source.agent.list() + for (const tool of manifest.tools) { + handles.push(registerToolProxy(target, source, tool)) + } + for (const resource of manifest.resources) { + handles.push(registerResourceProxy(target, source, resource)) + } + } + + sync() + source.agent.events.on('agent:manifest:changed', sync) +} + +function createViteDevToolsMcpDefinition(options: McpOptions) { + return defineDevframe({ + id: 'vite-devtools', + name: 'Vite DevTools', + version: packageJson.version, + async setup(ctx) { + const { startStandaloneDevTools } = await import('./standalone') + const { registerBuiltinAgents } = await import('./agents') + + const { context } = await startStandaloneDevTools({ + cwd: options.root, + builtinDevTools: false, + }) + await registerBuiltinAgents(context) + + mirrorAgentSurface(ctx, context) + }, + }) +} + +export async function startMcpServer(options: McpOptions = {}) { + const restoreConsole = redirectConsoleOutputToStderr() + let handle: Awaited> | undefined + try { + handle = await createMcpServer(createViteDevToolsMcpDefinition(options), { + transport: 'stdio', + serverName: 'vite-devtools', + serverVersion: packageJson.version, + }) + await waitForStdioClose() + } + finally { + await handle?.stop() + restoreConsole() + } +} diff --git a/packages/core/src/node/standalone.ts b/packages/core/src/node/standalone.ts index 37e688952..95172339c 100644 --- a/packages/core/src/node/standalone.ts +++ b/packages/core/src/node/standalone.ts @@ -10,6 +10,7 @@ export interface StandaloneDevToolsOptions { config?: string command?: 'build' | 'serve' mode?: 'development' | 'production' + builtinDevTools?: boolean } export async function startStandaloneDevTools(options: StandaloneDevToolsOptions = {}): Promise<{ @@ -20,6 +21,7 @@ export async function startStandaloneDevTools(options: StandaloneDevToolsOptions cwd = process.cwd(), command = 'build', mode = 'production', + builtinDevTools = true, } = options const { resolveConfig } = await import('vite') @@ -28,7 +30,7 @@ export async function startStandaloneDevTools(options: StandaloneDevToolsOptions configFile: options.config, root: cwd, plugins: [ - DevTools(), + DevTools({ builtinDevTools }), ], }, command, diff --git a/packages/rolldown/package.json b/packages/rolldown/package.json index 983e1cf5c..39c899257 100644 --- a/packages/rolldown/package.json +++ b/packages/rolldown/package.json @@ -20,6 +20,7 @@ "exports": { ".": "./dist/index.mjs", "./dirs": "./dist/dirs.mjs", + "./node/agent": "./dist/node/agent.mjs", "./package.json": "./package.json" }, "types": "./dist/index.d.mts", @@ -40,7 +41,6 @@ "birpc": "catalog:deps", "cac": "catalog:deps", "d3-shape": "catalog:frontend", - "devframe": "catalog:deps", "diff": "catalog:deps", "get-port-please": "catalog:deps", "h3": "catalog:deps", diff --git a/packages/rolldown/src/node/agent/analysis.ts b/packages/rolldown/src/node/agent/analysis.ts new file mode 100644 index 000000000..d6371532a --- /dev/null +++ b/packages/rolldown/src/node/agent/analysis.ts @@ -0,0 +1,29 @@ +import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' +import { createAnalysisContext } from './context' +import { createBuildAnalysis } from './modules/build-analysis' +import { createBuildComparison } from './modules/build-comparison' +import { createBuildTimeAnalysis } from './modules/build-time-analysis' +import { createBundleSizeAnalysis } from './modules/bundle-size-analysis' +import { createDependencyTrace } from './modules/dependency-trace' + +export type { + AnalysisInsight, + AnalysisReport, + BuildAnalysisInput, + BuildComparisonInput, + BuildTimeAnalysisInput, + BundleSizeAnalysisInput, + DependencyTraceInput, +} from './context' + +export function createRolldownAnalysis(context: ViteDevToolsNodeContext) { + const analysisContext = createAnalysisContext(context) + + return { + buildAnalysis: createBuildAnalysis(analysisContext), + buildTimeAnalysis: createBuildTimeAnalysis(analysisContext), + bundleSizeAnalysis: createBundleSizeAnalysis(analysisContext), + dependencyTrace: createDependencyTrace(analysisContext), + buildComparison: createBuildComparison(analysisContext), + } +} diff --git a/packages/rolldown/src/node/agent/context.ts b/packages/rolldown/src/node/agent/context.ts new file mode 100644 index 000000000..5fe7e1413 --- /dev/null +++ b/packages/rolldown/src/node/agent/context.ts @@ -0,0 +1,263 @@ +import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' +import type { ModuleBuildMetrics, RolldownAssetInfo, RolldownChunkInfo } from '../../shared/types' +import type { BuildInfo, RolldownLogsManager } from '../rolldown/logs-manager' +import { getPackageMeta } from '../rpc/functions/rolldown-get-packages' +import { getLogsManager } from '../rpc/utils' +import { getSessionTimestamp, sortByNumberDesc, sumBy } from './utils' + +export const SCHEMA_VERSION = 'rolldown-agent' + +export type AnalysisSeverity = 'info' | 'low' | 'medium' | 'high' +export type AnalysisCategory = 'build-time' | 'bundle-size' | 'dependency' | 'chunking' | 'plugin' | 'asset' | 'module' +export type AnalysisUnit = 'ms' | 'bytes' | 'count' | 'percent' | 'ratio' +export type AssetSessionReader = Awaited> + +export interface EvidenceSource { + type: 'session' | 'module' | 'package' | 'plugin' | 'chunk' | 'asset' + id: string +} + +export interface EvidenceItem { + label: string + value: string | number | boolean | null + unit?: AnalysisUnit + source?: EvidenceSource +} + +export interface AnalysisInsight { + id: string + category: AnalysisCategory + severity: AnalysisSeverity + title: string + explanation: string + evidence: EvidenceItem[] + recommendations?: string[] +} + +export interface AnalysisSession { + id: string + timestamp?: number +} + +export interface AnalysisReport { + schemaVersion: typeof SCHEMA_VERSION + tool: string + session?: AnalysisSession + answer: string + summary?: object + insights?: AnalysisInsight[] + limitations?: string[] +} + +export interface BuildAnalysisInput { + session?: string + issue?: 'general' | 'slow-build' | 'large-bundle' | 'unexpected-dependency' | 'chunking' | 'dependency-duplication' + limit?: number +} + +export interface BuildTimeAnalysisInput { + session?: string + limit?: number +} + +export interface BundleSizeAnalysisInput { + session?: string + scope?: 'all' | 'initial' | 'async' | 'assets' + limit?: number +} + +export interface DependencyTraceInput { + session?: string + target: { + type: 'module' | 'package' | 'asset' + id: string + } + direction?: 'importers' | 'imports' | 'both' + maxDepth?: number + limit?: number +} + +export interface BuildComparisonInput { + baseSession: string + currentSession: string + limit?: number +} + +export interface ResolvedSession { + id?: string + info?: BuildInfo + sessions: BuildInfo[] + report?: AnalysisReport +} + +export interface SessionStats { + buildDuration: number + modules: number + chunks: number + assets: number + plugins: number + bundleSize: number + initialJs: number + packageGraphSupported: boolean + packages: number + duplicatedPackages: number +} + +export interface ModuleCost { + id: string + totalDuration: number + resolveDuration: number + loadDuration: number + transformDuration: number +} + +export interface AgentAnalysisContext { + manager: RolldownLogsManager + listSessions: () => Promise + resolveSession: (tool: string, requested?: string) => Promise +} + +export function createEmptyReport(tool: string, answer: string, limitations: string[] = []): AnalysisReport { + return { + schemaVersion: SCHEMA_VERSION, + tool, + answer, + limitations, + } +} + +export function getBuildDuration(reader: AssetSessionReader) { + return Math.max(0, reader.manager.build_end_time - reader.manager.build_start_time) +} + +export function getAssetScope(asset: RolldownAssetInfo, chunks: Map) { + if (asset.chunk_id == null) + return 'static' + return chunks.get(asset.chunk_id)?.is_initial ? 'initial' : 'async' +} + +export function getModuleTransformedSize(reader: AssetSessionReader, id: string) { + const transforms = reader.manager.modules.get(id)?.build_metrics?.transforms + return transforms?.at(-1)?.transformed_code_size ?? 0 +} + +export function getChunkSize(reader: AssetSessionReader, chunk: RolldownChunkInfo) { + const asset = reader.manager.chunkAssetMap.get(chunk.chunk_id) + if (asset) + return asset.size + + return chunk.modules.reduce((total, id) => total + getModuleTransformedSize(reader, id), 0) +} + +export function getModuleCost(id: string, metrics: ModuleBuildMetrics | undefined): ModuleCost { + const resolveDuration = sumBy(metrics?.resolve_ids ?? [], item => item.duration) + const loadDuration = sumBy(metrics?.loads ?? [], item => item.duration) + const transformDuration = sumBy(metrics?.transforms ?? [], item => item.duration) + + return { + id, + totalDuration: resolveDuration + loadDuration + transformDuration, + resolveDuration, + loadDuration, + transformDuration, + } +} + +export function getTopModuleCosts(reader: AssetSessionReader, limit: number) { + return sortByNumberDesc( + Array.from(reader.manager.modules.entries()) + .map(([id, module]) => getModuleCost(id, module.build_metrics)), + item => item.totalDuration, + ).slice(0, limit) +} + +export function createSessionStats(reader: AssetSessionReader): SessionStats { + const assets = Array.from(reader.manager.assets.values()) + const chunks = Array.from(reader.manager.chunks.values()) + const initialChunkIds = new Set(chunks.filter(chunk => chunk.is_initial).map(chunk => chunk.chunk_id)) + const packageMeta = getPackageMeta(reader) + + return { + buildDuration: getBuildDuration(reader), + modules: reader.manager.modules.size, + chunks: chunks.length, + assets: assets.length, + plugins: reader.meta?.plugins?.length ?? 0, + bundleSize: sumBy(assets, asset => asset.size), + initialJs: sumBy(assets.filter(asset => asset.chunk_id != null && initialChunkIds.has(asset.chunk_id)), asset => asset.size), + packageGraphSupported: packageMeta.isSupported, + packages: packageMeta.packages.length, + duplicatedPackages: packageMeta.packages.filter(pkg => pkg.duplicated).length, + } +} + +export function createSessionReport( + tool: string, + session: string, + sessions: BuildInfo[], + data: Omit, +): AnalysisReport { + return { + schemaVersion: SCHEMA_VERSION, + tool, + session: { + id: session, + timestamp: getSessionTimestamp(sessions, session), + }, + ...data, + } +} + +export function createSessionNotFoundReport(tool: string, session: string, sessions: BuildInfo[]): AnalysisReport { + return createEmptyReport( + tool, + `Rolldown session "${session}" was not found.`, + [ + sessions.length + ? `Available sessions: ${sessions.map(item => item.id).join(', ')}.` + : 'No Rolldown sessions were found. Run a build with Rolldown devtools output enabled first.', + ], + ) +} + +export function createAnalysisContext(context: ViteDevToolsNodeContext): AgentAnalysisContext { + const manager = getLogsManager(context) + + async function listSessions() { + const sessions = await manager.list() + return sessions.toSorted((a, b) => b.timestamp - a.timestamp) + } + + async function resolveSession(tool: string, requested?: string): Promise { + const sessions = await listSessions() + const id = !requested || requested === 'latest' + ? sessions[0]?.id + : requested + + if (!id) { + return { + sessions, + report: createEmptyReport(tool, 'No Rolldown sessions were found.', [ + 'Run a build with Rolldown devtools output enabled before using this tool.', + ]), + } + } + + const info = sessions.find(session => session.id === id) + if (!info && requested && requested !== 'latest') { + return { + id, + sessions, + report: createSessionNotFoundReport(tool, id, sessions), + } + } + + return { id, info, sessions } + } + + return { + manager, + listSessions, + resolveSession, + } +} diff --git a/packages/rolldown/src/node/agent/index.ts b/packages/rolldown/src/node/agent/index.ts new file mode 100644 index 000000000..4cf5611b4 --- /dev/null +++ b/packages/rolldown/src/node/agent/index.ts @@ -0,0 +1,11 @@ +import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' +import { registerRolldownAgentTools } from './tools' + +export const rolldownAgent = { + namespace: 'rolldown', + setup(ctx: ViteDevToolsNodeContext) { + registerRolldownAgentTools(ctx) + }, +} as const + +export { registerRolldownAgentTools } from './tools' diff --git a/packages/rolldown/src/node/agent/modules/build-analysis.ts b/packages/rolldown/src/node/agent/modules/build-analysis.ts new file mode 100644 index 000000000..cdb35ec2e --- /dev/null +++ b/packages/rolldown/src/node/agent/modules/build-analysis.ts @@ -0,0 +1,159 @@ +import type { AgentAnalysisContext, AnalysisCategory, AnalysisInsight, AnalysisReport, BuildAnalysisInput } from '../context' +import { getPackageMeta } from '../../rpc/functions/rolldown-get-packages' +import { + createSessionReport, + createSessionStats, + getAssetScope, + getChunkSize, +} from '../context' +import { + clampLimit, + percentage, + sortByNumberDesc, + sumBy, +} from '../utils' + +function getIssueCategory(issue: BuildAnalysisInput['issue']): AnalysisCategory | undefined { + if (issue === 'slow-build') + return 'build-time' + if (issue === 'large-bundle') + return 'bundle-size' + if (issue === 'unexpected-dependency' || issue === 'dependency-duplication') + return 'dependency' + if (issue === 'chunking') + return 'chunking' +} + +function prioritizeInsights(insights: AnalysisInsight[], issue: BuildAnalysisInput['issue']) { + const category = getIssueCategory(issue) + if (!category) + return insights + + return insights.toSorted((a, b) => { + const aMatch = a.category === category ? 1 : 0 + const bMatch = b.category === category ? 1 : 0 + return bMatch - aMatch + }) +} + +export function createBuildAnalysis(context: AgentAnalysisContext) { + const { manager, resolveSession } = context + + return async function buildAnalysis(input: BuildAnalysisInput = {}): Promise { + const tool = 'rolldown:build-analysis' + const limit = clampLimit(input.limit) + const resolved = await resolveSession(tool, input.session) + if (resolved.report || !resolved.id) + return resolved.report! + + const reader = await manager.loadAssetSession(resolved.id) + const stats = createSessionStats(reader) + const pluginSummaries = Array.from((await reader.readPluginBuildMetricsSummary()).values()) + const measuredPluginDuration = sumBy(pluginSummaries, item => item.total.duration) + const transformDuration = sumBy(pluginSummaries, item => item.transform.duration) + const chunks = Array.from(reader.manager.chunks.values()) + const packageMeta = getPackageMeta(reader) + const packagesBySize = sortByNumberDesc(packageMeta.packages, pkg => pkg.transformedCodeSize) + const largestPackage = packagesBySize[0] + const assetsBySize = sortByNumberDesc(Array.from(reader.manager.assets.values()), asset => asset.size) + const largestAsset = assetsBySize[0] + const chunksBySize = sortByNumberDesc(chunks, chunk => getChunkSize(reader, chunk)) + const largestChunk = chunksBySize[0] + const duplicatedPackages = packageMeta.packages.filter(pkg => pkg.duplicated) + + const insights: AnalysisInsight[] = [] + + if (measuredPluginDuration > 0 && transformDuration / measuredPluginDuration >= 0.5) { + insights.push({ + id: 'build-time:transform-dominates', + category: 'build-time', + severity: 'medium', + title: 'Transform hooks dominate measured plugin time', + explanation: 'Most measured plugin time is spent in transform hooks, so plugin transforms are the first area to inspect for build-time cost.', + evidence: [ + { label: 'Transform hook time', value: transformDuration, unit: 'ms' }, + { label: 'Measured plugin time', value: measuredPluginDuration, unit: 'ms' }, + { label: 'Share', value: percentage(transformDuration, measuredPluginDuration), unit: 'ratio' }, + ], + recommendations: ['Run rolldown:build-time-analysis to inspect top plugins and modules.'], + }) + } + + if (largestPackage && stats.bundleSize > 0 && largestPackage.transformedCodeSize / stats.bundleSize >= 0.2) { + insights.push({ + id: `bundle-size:large-package:${largestPackage.name}`, + category: 'bundle-size', + severity: 'medium', + title: `Package "${largestPackage.name}" is a large bundle contributor`, + explanation: 'A single package accounts for a large share of transformed bundled code.', + evidence: [ + { label: 'Package size', value: largestPackage.transformedCodeSize, unit: 'bytes', source: { type: 'package', id: largestPackage.id } }, + { label: 'Bundle size', value: stats.bundleSize, unit: 'bytes', source: { type: 'session', id: resolved.id } }, + { label: 'Share', value: percentage(largestPackage.transformedCodeSize, stats.bundleSize), unit: 'ratio' }, + ], + recommendations: [`Run rolldown:dependency-trace for package "${largestPackage.name}" to find importer paths.`], + }) + } + + if (largestAsset && stats.bundleSize > 0 && largestAsset.size / stats.bundleSize >= 0.35) { + insights.push({ + id: `asset:large-output:${largestAsset.filename}`, + category: 'asset', + severity: 'low', + title: `Asset "${largestAsset.filename}" is the largest output`, + explanation: 'The largest emitted asset takes a substantial share of total output size.', + evidence: [ + { label: 'Asset size', value: largestAsset.size, unit: 'bytes', source: { type: 'asset', id: largestAsset.filename } }, + { label: 'Scope', value: getAssetScope(largestAsset, new Map(chunks.map(chunk => [chunk.chunk_id, chunk]))) }, + ], + }) + } + + if (largestChunk) { + const chunkSize = getChunkSize(reader, largestChunk) + if (stats.bundleSize > 0 && chunkSize / stats.bundleSize >= 0.35) { + insights.push({ + id: `chunking:large-chunk:${largestChunk.chunk_id}`, + category: 'chunking', + severity: largestChunk.is_initial ? 'medium' : 'low', + title: `Chunk "${largestChunk.name || largestChunk.chunk_id}" is a large output unit`, + explanation: largestChunk.is_initial + ? 'A large initial chunk can increase startup cost because it is needed for initial loading.' + : 'A large async chunk can still affect the route or interaction that loads it.', + evidence: [ + { label: 'Chunk size', value: chunkSize, unit: 'bytes', source: { type: 'chunk', id: String(largestChunk.chunk_id) } }, + { label: 'Modules in chunk', value: largestChunk.modules.length, unit: 'count', source: { type: 'chunk', id: String(largestChunk.chunk_id) } }, + { label: 'Initial chunk', value: !!largestChunk.is_initial }, + ], + }) + } + } + + if (duplicatedPackages.length) { + insights.push({ + id: 'dependency:duplicated-packages', + category: 'dependency', + severity: duplicatedPackages.length > 3 ? 'medium' : 'low', + title: 'Duplicated packages were detected', + explanation: 'Multiple versions or physical copies of the same package family are present in the bundle.', + evidence: [ + { label: 'Duplicated package entries', value: duplicatedPackages.length, unit: 'count' }, + { label: 'Examples', value: duplicatedPackages.slice(0, limit).map(pkg => `${pkg.name}@${pkg.version}`).join(', ') }, + ], + recommendations: ['Run rolldown:bundle-size-analysis to inspect dependency contributors.'], + }) + } + + return createSessionReport(tool, resolved.id, resolved.sessions, { + answer: insights.length + ? `The build completed in ${stats.buildDuration}ms with ${insights.length} notable insight(s).` + : `The build completed in ${stats.buildDuration}ms. No major heuristic insights were detected by the first-pass analysis.`, + summary: stats, + insights: prioritizeInsights(insights, input.issue).slice(0, limit), + limitations: [ + 'This is a heuristic first-pass analysis based on Rolldown debug logs.', + 'Tree-shaking effectiveness is inferred from emitted and transformed sizes; unused export removal is not proven by this report.', + ], + }) + } +} diff --git a/packages/rolldown/src/node/agent/modules/build-comparison.ts b/packages/rolldown/src/node/agent/modules/build-comparison.ts new file mode 100644 index 000000000..f08a7a36a --- /dev/null +++ b/packages/rolldown/src/node/agent/modules/build-comparison.ts @@ -0,0 +1,117 @@ +import type { AgentAnalysisContext, AnalysisInsight, AnalysisReport, BuildComparisonInput } from '../context' +import { createSessionCompareDetails } from '../../rpc/functions/rolldown-get-session-compare-details' +import { createEmptyReport, createSessionStats, SCHEMA_VERSION } from '../context' +import { clampLimit } from '../utils' + +export function createBuildComparison(context: AgentAnalysisContext) { + const { manager, listSessions } = context + + return async function buildComparison(input: BuildComparisonInput): Promise { + const tool = 'rolldown:build-comparison' + const limit = clampLimit(input?.limit) + const sessions = await listSessions() + const baseSession = input?.baseSession + const currentSession = input?.currentSession + + if (!baseSession || !currentSession) { + return createEmptyReport(tool, 'Both baseSession and currentSession are required.', [ + sessions.length + ? `Available sessions: ${sessions.map(item => item.id).join(', ')}.` + : 'No Rolldown sessions were found.', + ]) + } + + const baseInfo = sessions.find(session => session.id === baseSession) + const currentInfo = sessions.find(session => session.id === currentSession) + if (!baseInfo || !currentInfo) { + return createEmptyReport(tool, 'One or both requested sessions were not found.', [ + `Missing sessions: ${[ + baseInfo ? undefined : baseSession, + currentInfo ? undefined : currentSession, + ].filter(Boolean).join(', ')}.`, + ]) + } + + const [baseReader, currentReader] = await Promise.all([ + manager.loadAssetSession(baseSession), + manager.loadAssetSession(currentSession), + ]) + const baseStats = createSessionStats(baseReader) + const currentStats = createSessionStats(currentReader) + const compareDetails = await createSessionCompareDetails(baseReader, currentReader) + + const deltas = { + buildDuration: currentStats.buildDuration - baseStats.buildDuration, + bundleSize: currentStats.bundleSize - baseStats.bundleSize, + initialJs: currentStats.initialJs - baseStats.initialJs, + modules: currentStats.modules - baseStats.modules, + chunks: currentStats.chunks - baseStats.chunks, + assets: currentStats.assets - baseStats.assets, + duplicatedPackages: currentStats.duplicatedPackages - baseStats.duplicatedPackages, + } + + const insights: AnalysisInsight[] = [] + if (deltas.buildDuration !== 0) { + insights.push({ + id: 'comparison:build-duration', + category: 'build-time', + severity: deltas.buildDuration > 0 ? 'medium' : 'info', + title: deltas.buildDuration > 0 ? 'Build duration increased' : 'Build duration decreased', + explanation: 'The overall build duration changed between the selected sessions.', + evidence: [ + { label: 'Previous build duration', value: baseStats.buildDuration, unit: 'ms', source: { type: 'session', id: baseSession } }, + { label: 'Current build duration', value: currentStats.buildDuration, unit: 'ms', source: { type: 'session', id: currentSession } }, + { label: 'Delta', value: deltas.buildDuration, unit: 'ms' }, + ], + }) + } + if (deltas.bundleSize !== 0) { + insights.push({ + id: 'comparison:bundle-size', + category: 'bundle-size', + severity: deltas.bundleSize > 0 ? 'medium' : 'info', + title: deltas.bundleSize > 0 ? 'Bundle size increased' : 'Bundle size decreased', + explanation: 'Total emitted asset size changed between the selected sessions.', + evidence: [ + { label: 'Previous bundle size', value: baseStats.bundleSize, unit: 'bytes', source: { type: 'session', id: baseSession } }, + { label: 'Current bundle size', value: currentStats.bundleSize, unit: 'bytes', source: { type: 'session', id: currentSession } }, + { label: 'Delta', value: deltas.bundleSize, unit: 'bytes' }, + ], + }) + } + + const topAssets = compareDetails.assets.slice(0, limit) + const topChunks = compareDetails.chunks.slice(0, limit) + const topPackages = compareDetails.packages.slice(0, limit) + const topPlugins = compareDetails.plugins.slice(0, limit) + + return { + schemaVersion: SCHEMA_VERSION, + tool, + answer: `Compared "${baseSession}" to "${currentSession}". Bundle size delta: ${deltas.bundleSize} bytes. Build duration delta: ${deltas.buildDuration}ms.`, + summary: { + previous: { + session: baseSession, + timestamp: baseInfo.timestamp, + ...baseStats, + }, + current: { + session: currentSession, + timestamp: currentInfo.timestamp, + ...currentStats, + }, + deltas, + topAssets, + topChunks, + topPackages, + topPlugins, + sessionStats: compareDetails.sessionStats, + }, + insights: insights.slice(0, limit), + limitations: [ + 'Asset and chunk matching normalizes hashed filenames where possible, but renamed or heavily restructured outputs may still appear as added and removed items.', + 'Plugin comparison uses recorded hook durations and call counts; non-plugin build work is represented only in total build duration.', + ], + } + } +} diff --git a/packages/rolldown/src/node/agent/modules/build-time-analysis.ts b/packages/rolldown/src/node/agent/modules/build-time-analysis.ts new file mode 100644 index 000000000..c83e18973 --- /dev/null +++ b/packages/rolldown/src/node/agent/modules/build-time-analysis.ts @@ -0,0 +1,110 @@ +import type { AgentAnalysisContext, AnalysisInsight, AnalysisReport, BuildTimeAnalysisInput } from '../context' +import { + createSessionReport, + createSessionStats, + getTopModuleCosts, +} from '../context' +import { + clampLimit, + percentage, + sortByNumberDesc, + sumBy, +} from '../utils' + +export function createBuildTimeAnalysis(context: AgentAnalysisContext) { + const { manager, resolveSession } = context + + return async function buildTimeAnalysis(input: BuildTimeAnalysisInput = {}): Promise { + const tool = 'rolldown:build-time-analysis' + const limit = clampLimit(input.limit) + const resolved = await resolveSession(tool, input.session) + if (resolved.report || !resolved.id) + return resolved.report! + + const reader = await manager.loadAssetSession(resolved.id) + const stats = createSessionStats(reader) + const pluginSummaries = Array.from((await reader.readPluginBuildMetricsSummary()).values()) + const hookBreakdown = { + resolve: { + duration: sumBy(pluginSummaries, item => item.resolve.duration), + calls: sumBy(pluginSummaries, item => item.resolve.count), + }, + load: { + duration: sumBy(pluginSummaries, item => item.load.duration), + calls: sumBy(pluginSummaries, item => item.load.count), + }, + transform: { + duration: sumBy(pluginSummaries, item => item.transform.duration), + calls: sumBy(pluginSummaries, item => item.transform.count), + }, + } + const measuredPluginDuration = hookBreakdown.resolve.duration + hookBreakdown.load.duration + hookBreakdown.transform.duration + const topPlugins = sortByNumberDesc(pluginSummaries, item => item.total.duration) + .slice(0, limit) + .map(plugin => ({ + pluginId: plugin.plugin_id, + name: plugin.plugin_name, + totalDuration: plugin.total.duration, + totalCalls: plugin.total.count, + resolveDuration: plugin.resolve.duration, + resolveCalls: plugin.resolve.count, + loadDuration: plugin.load.duration, + loadCalls: plugin.load.count, + transformDuration: plugin.transform.duration, + transformCalls: plugin.transform.count, + })) + const topModules = getTopModuleCosts(reader, limit) + + const insights: AnalysisInsight[] = [] + const topPlugin = topPlugins[0] + if (topPlugin && measuredPluginDuration > 0) { + insights.push({ + id: `plugin:top-cost:${topPlugin.pluginId}`, + category: 'plugin', + severity: percentage(topPlugin.totalDuration, measuredPluginDuration) >= 0.35 ? 'medium' : 'low', + title: `Plugin "${topPlugin.name}" has the highest measured hook cost`, + explanation: 'This plugin accounts for the largest share of measured plugin hook duration.', + evidence: [ + { label: 'Plugin duration', value: topPlugin.totalDuration, unit: 'ms', source: { type: 'plugin', id: String(topPlugin.pluginId) } }, + { label: 'Plugin calls', value: topPlugin.totalCalls, unit: 'count', source: { type: 'plugin', id: String(topPlugin.pluginId) } }, + { label: 'Share of measured plugin time', value: percentage(topPlugin.totalDuration, measuredPluginDuration), unit: 'ratio' }, + ], + }) + } + + const topModule = topModules[0] + if (topModule && topModule.totalDuration > 0) { + insights.push({ + id: `module:top-build-cost:${topModule.id}`, + category: 'module', + severity: 'low', + title: `Module "${topModule.id}" has the highest measured build cost`, + explanation: 'This module has the largest combined resolve, load, and transform duration in the current build data.', + evidence: [ + { label: 'Module duration', value: topModule.totalDuration, unit: 'ms', source: { type: 'module', id: topModule.id } }, + { label: 'Resolve duration', value: topModule.resolveDuration, unit: 'ms' }, + { label: 'Load duration', value: topModule.loadDuration, unit: 'ms' }, + { label: 'Transform duration', value: topModule.transformDuration, unit: 'ms' }, + ], + }) + } + + return createSessionReport(tool, resolved.id, resolved.sessions, { + answer: measuredPluginDuration > 0 + ? `Measured plugin hooks took ${measuredPluginDuration}ms. The top plugin is "${topPlugins[0]?.name ?? 'unknown'}".` + : 'No measured plugin hook duration was found in this session.', + summary: { + buildDuration: stats.buildDuration, + measuredPluginDuration, + hookBreakdown, + topPlugins, + topModules, + }, + insights, + limitations: [ + 'Build duration and measured plugin hook duration are related but not identical; Rolldown work outside plugin hooks is not attributed to a plugin.', + 'Module cost is based on recorded resolve, load, and transform hook metrics.', + ], + }) + } +} diff --git a/packages/rolldown/src/node/agent/modules/bundle-size-analysis.ts b/packages/rolldown/src/node/agent/modules/bundle-size-analysis.ts new file mode 100644 index 000000000..ff2559a1e --- /dev/null +++ b/packages/rolldown/src/node/agent/modules/bundle-size-analysis.ts @@ -0,0 +1,134 @@ +import type { AgentAnalysisContext, AnalysisInsight, AnalysisReport, BundleSizeAnalysisInput } from '../context' +import { getPackageMeta } from '../../rpc/functions/rolldown-get-packages' +import { + createSessionReport, + createSessionStats, + getAssetScope, + getChunkSize, +} from '../context' +import { + clampLimit, + percentage, + sortByNumberDesc, + sumBy, +} from '../utils' + +export function createBundleSizeAnalysis(context: AgentAnalysisContext) { + const { manager, resolveSession } = context + + return async function bundleSizeAnalysis(input: BundleSizeAnalysisInput = {}): Promise { + const tool = 'rolldown:bundle-size-analysis' + const limit = clampLimit(input.limit) + const scope = input.scope ?? 'all' + const resolved = await resolveSession(tool, input.session) + if (resolved.report || !resolved.id) + return resolved.report! + + const reader = await manager.loadAssetSession(resolved.id) + const stats = createSessionStats(reader) + const chunks = Array.from(reader.manager.chunks.values()) + const chunkMap = new Map(chunks.map(chunk => [chunk.chunk_id, chunk])) + const packageMeta = getPackageMeta(reader) + const analyzedAssets = Array.from(reader.manager.assets.values()) + .filter((asset) => { + if (scope === 'assets') + return asset.chunk_id == null + if (scope === 'initial') + return asset.chunk_id != null && chunkMap.get(asset.chunk_id)?.is_initial + if (scope === 'async') + return asset.chunk_id != null && !chunkMap.get(asset.chunk_id)?.is_initial + return true + }) + const analyzedAssetSize = sumBy(analyzedAssets, asset => asset.size) + const largestAssets = sortByNumberDesc(analyzedAssets, asset => asset.size) + .slice(0, limit) + .map(asset => ({ + filename: asset.filename, + size: asset.size, + scope: getAssetScope(asset, chunkMap), + chunkId: asset.chunk_id, + share: percentage(asset.size, analyzedAssetSize), + })) + const largestChunks = sortByNumberDesc(chunks, chunk => getChunkSize(reader, chunk)) + .slice(0, limit) + .map(chunk => ({ + chunkId: chunk.chunk_id, + name: chunk.name, + reason: chunk.reason, + initial: !!chunk.is_initial, + modules: chunk.modules.length, + size: getChunkSize(reader, chunk), + })) + const largestPackages = sortByNumberDesc(packageMeta.packages, pkg => pkg.transformedCodeSize) + .slice(0, limit) + .map(pkg => ({ + id: pkg.id, + name: pkg.name, + version: pkg.version, + type: pkg.type, + size: pkg.transformedCodeSize, + files: pkg.files.length, + duplicated: !!pkg.duplicated, + share: percentage(pkg.transformedCodeSize, stats.bundleSize), + })) + const duplicatedPackages = packageMeta.packages + .filter(pkg => pkg.duplicated) + .map(pkg => ({ + id: pkg.id, + name: pkg.name, + version: pkg.version, + size: pkg.transformedCodeSize, + })) + .slice(0, limit) + + const insights: AnalysisInsight[] = [] + const largestPackage = largestPackages[0] + if (largestPackage) { + insights.push({ + id: `bundle-size:top-package:${largestPackage.id}`, + category: 'dependency', + severity: largestPackage.share >= 0.2 ? 'medium' : 'low', + title: `Package "${largestPackage.name}" is the largest package contributor`, + explanation: 'This package contributes the largest transformed code size among package graph entries.', + evidence: [ + { label: 'Package size', value: largestPackage.size, unit: 'bytes', source: { type: 'package', id: largestPackage.id } }, + { label: 'Share of total emitted assets', value: largestPackage.share, unit: 'ratio' }, + { label: 'Bundled files', value: largestPackage.files, unit: 'count' }, + ], + }) + } + if (duplicatedPackages.length) { + insights.push({ + id: 'bundle-size:duplicated-packages', + category: 'dependency', + severity: duplicatedPackages.length > 3 ? 'medium' : 'low', + title: 'Duplicated package entries increase bundle complexity', + explanation: 'Duplicated package entries can indicate multiple versions or duplicated physical package copies in the bundled dependency graph.', + evidence: [ + { label: 'Duplicated package entries', value: duplicatedPackages.length, unit: 'count' }, + { label: 'Examples', value: duplicatedPackages.map(pkg => `${pkg.name}@${pkg.version}`).join(', ') }, + ], + }) + } + + return createSessionReport(tool, resolved.id, resolved.sessions, { + answer: `The selected scope contains ${analyzedAssetSize} bytes across ${analyzedAssets.length} asset(s).`, + summary: { + scope, + totalAssetSize: analyzedAssetSize, + bundleSize: stats.bundleSize, + initialJs: stats.initialJs, + topAssets: largestAssets, + topChunks: largestChunks, + packageGraphSupported: packageMeta.isSupported, + topPackages: largestPackages, + duplicatedPackages, + }, + insights, + limitations: [ + 'Package sizes are based on transformed module sizes attributed to package graph entries.', + 'Chunk sizes prefer emitted asset size and fall back to summed transformed module size when an emitted asset is unavailable.', + ], + }) + } +} diff --git a/packages/rolldown/src/node/agent/modules/dependency-trace.ts b/packages/rolldown/src/node/agent/modules/dependency-trace.ts new file mode 100644 index 000000000..495e527ca --- /dev/null +++ b/packages/rolldown/src/node/agent/modules/dependency-trace.ts @@ -0,0 +1,204 @@ +import type { PackageInfo, RolldownAssetInfo } from '../../../shared/types' +import type { AgentAnalysisContext, AnalysisReport, DependencyTraceInput } from '../context' +import { getPackageMeta } from '../../rpc/functions/rolldown-get-packages' +import { createSessionReport, getChunkSize } from '../context' +import { clampDepth, clampLimit } from '../utils' + +type ModuleGraph = Map + importers?: string[] +}> + +function isTargetPackage(pkg: PackageInfo, target: string) { + return pkg.id === target + || pkg.name === target + || `${pkg.name}@${pkg.version}` === target + || pkg.dir === target +} + +function getGraphPaths( + targets: string[], + maxDepth: number, + limit: number, + getNext: (id: string) => string[], + options: { reverse?: boolean } = {}, +) { + const paths: string[][] = [] + const queue = targets.map(id => ({ id, path: [id] })) + + while (queue.length && paths.length < limit) { + const current = queue.shift()! + const nextIds = getNext(current.id) + + if (!nextIds.length || current.path.length >= maxDepth) { + paths.push(options.reverse ? current.path.toReversed() : current.path) + continue + } + + for (const nextId of nextIds) { + if (paths.length + queue.length >= limit) + break + if (current.path.includes(nextId)) + continue + queue.push({ + id: nextId, + path: [...current.path, nextId], + }) + } + } + + return paths +} + +function getImporterPaths( + modules: ModuleGraph, + targets: string[], + maxDepth: number, + limit: number, +) { + return getGraphPaths( + targets, + maxDepth, + limit, + id => modules.get(id)?.importers?.filter(importer => modules.has(importer)) ?? [], + { reverse: true }, + ) +} + +function getImportPaths( + modules: ModuleGraph, + targets: string[], + maxDepth: number, + limit: number, +) { + return getGraphPaths( + targets, + maxDepth, + limit, + id => modules.get(id)?.imports?.map(item => item.module_id).filter(imported => modules.has(imported)) ?? [], + ) +} + +function formatTraceAnswer( + target: DependencyTraceInput['target'], + targetLabel: string, + importerPaths: string[][], + importPaths: string[][], +) { + if (importerPaths[0]) + return `${target.type} "${targetLabel}" is included through ${importerPaths[0].join(' -> ')}.` + if (importPaths[0]) + return `${target.type} "${targetLabel}" has an import path: ${importPaths[0].join(' -> ')}.` + return `${target.type} "${targetLabel}" was found in the bundle, but no graph path was resolved within the requested depth.` +} + +export function createDependencyTrace(context: AgentAnalysisContext) { + const { manager, resolveSession } = context + + return async function dependencyTrace(input: DependencyTraceInput): Promise { + const tool = 'rolldown:dependency-trace' + const limit = clampLimit(input?.limit) + const maxDepth = clampDepth(input?.maxDepth) + const direction = input?.direction ?? 'importers' + const resolved = await resolveSession(tool, input?.session) + if (resolved.report || !resolved.id) + return resolved.report! + + const reader = await manager.loadAssetSession(resolved.id) + const packageMeta = getPackageMeta(reader) + const modules = reader.manager.modules as ModuleGraph + const chunks = Array.from(reader.manager.chunks.values()) + const chunkMap = new Map(chunks.map(chunk => [chunk.chunk_id, chunk])) + const target = input?.target + + if (!target) { + return createSessionReport(tool, resolved.id, resolved.sessions, { + answer: 'No trace target was provided.', + limitations: ['Provide target.type and target.id to trace module, package, or asset inclusion.'], + }) + } + + let matchedModuleIds: string[] = [] + let targetLabel = target.id + let matchedPackages: PackageInfo[] = [] + let matchedAsset: RolldownAssetInfo | undefined + + if (target.type === 'module') { + matchedModuleIds = modules.has(target.id) + ? [target.id] + : Array.from(modules.keys()).filter(id => id.includes(target.id)).slice(0, limit) + } + else if (target.type === 'package') { + matchedPackages = packageMeta.packages.filter(pkg => isTargetPackage(pkg, target.id)) + matchedModuleIds = Array.from(new Set(matchedPackages.flatMap(pkg => pkg.files.map(file => file.path)))) + targetLabel = matchedPackages[0]?.name ?? target.id + } + else if (target.type === 'asset') { + matchedAsset = reader.manager.assets.get(target.id) + ?? Array.from(reader.manager.assets.values()).find(asset => asset.filename.includes(target.id)) + const chunk = matchedAsset?.chunk_id == null ? undefined : chunkMap.get(matchedAsset.chunk_id) + matchedModuleIds = chunk?.entry_module + ? [chunk.entry_module] + : chunk?.modules.slice(0, limit) ?? [] + targetLabel = matchedAsset?.filename ?? target.id + } + + matchedModuleIds = matchedModuleIds.filter(module => modules.has(module)).slice(0, limit) + + if (!matchedModuleIds.length) { + return createSessionReport(tool, resolved.id, resolved.sessions, { + answer: `No bundled module was found for ${target.type} "${target.id}".`, + summary: { + target, + packageGraphSupported: packageMeta.isSupported, + }, + limitations: [ + target.type === 'package' + ? 'Package tracing requires Rolldown package graph data.' + : 'The target may be external, removed by bundling, or represented by a different module id.', + ], + }) + } + + const importerPaths = direction === 'imports' + ? [] + : getImporterPaths(modules, matchedModuleIds, maxDepth, limit) + const importPaths = direction === 'importers' + ? [] + : getImportPaths(modules, matchedModuleIds, maxDepth, limit) + const matchedModuleIdSet = new Set(matchedModuleIds) + const relatedChunks = chunks + .filter(chunk => chunk.modules.some(module => matchedModuleIdSet.has(module))) + .map(chunk => ({ + chunkId: chunk.chunk_id, + name: chunk.name, + initial: !!chunk.is_initial, + size: getChunkSize(reader, chunk), + modules: chunk.modules.length, + })) + .slice(0, limit) + + return createSessionReport(tool, resolved.id, resolved.sessions, { + answer: formatTraceAnswer(target, targetLabel, importerPaths, importPaths), + summary: { + target, + matchedModules: matchedModuleIds, + matchedPackages: matchedPackages.map(pkg => ({ id: pkg.id, name: pkg.name, version: pkg.version, files: pkg.files.length })), + matchedAsset: matchedAsset + ? { + filename: matchedAsset.filename, + size: matchedAsset.size, + chunkId: matchedAsset.chunk_id, + } + : undefined, + importerPaths, + importPaths, + relatedChunks, + }, + limitations: [ + 'Graph paths are derived from the recorded module graph and are capped by maxDepth and limit.', + 'A path ending before an entry module can indicate a graph root, an external boundary, or the configured depth limit.', + ], + }) + } +} diff --git a/packages/rolldown/src/node/agent/tools.ts b/packages/rolldown/src/node/agent/tools.ts new file mode 100644 index 000000000..e599ec824 --- /dev/null +++ b/packages/rolldown/src/node/agent/tools.ts @@ -0,0 +1,205 @@ +import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' +import type { + BuildAnalysisInput, + BuildComparisonInput, + BuildTimeAnalysisInput, + BundleSizeAnalysisInput, + DependencyTraceInput, +} from './analysis' +import { createRolldownAnalysis } from './analysis' + +const sessionProperty = { + type: 'string', + description: 'Rolldown session id to analyze. Use "latest" or omit this field to analyze the newest session.', +} as const + +const limitProperty = { + type: 'number', + description: 'Maximum number of ranked insights, items, or paths to return. Defaults to 5 and is capped at 200.', + minimum: 1, + maximum: 200, +} as const + +const readOnlyTags = ['rolldown', 'build-analysis'] as const + +const outputSchema = { + type: 'object', + additionalProperties: true, +} as const + +export function registerRolldownAgentTools(ctx: ViteDevToolsNodeContext) { + if (ctx.agent.getTool('rolldown:build-analysis')) + return + + let analysis: ReturnType | undefined + const getAnalysis = () => { + analysis ??= createRolldownAnalysis(ctx) + return analysis + } + + ctx.agent.registerTool({ + id: 'rolldown:build-analysis', + title: 'Rolldown build analysis', + description: 'Analyze a Rolldown build session and return a high-level explanation with notable insights across build time, bundle size, chunks, assets, and dependencies. Use this as the default entry point when the user asks what happened in a build or reports an unclear build problem.', + safety: 'read', + tags: readOnlyTags, + inputSchema: { + type: 'object', + properties: { + session: sessionProperty, + issue: { + type: 'string', + description: 'Optional issue hint that helps prioritize insights.', + enum: ['general', 'slow-build', 'large-bundle', 'unexpected-dependency', 'chunking', 'dependency-duplication'], + }, + limit: limitProperty, + }, + additionalProperties: false, + }, + outputSchema, + examples: [ + { + args: [{ session: 'latest', issue: 'general', limit: 5 }], + description: 'Explain the latest build and return the top insights.', + }, + ], + handler: args => getAnalysis().buildAnalysis(args as BuildAnalysisInput), + }) + + ctx.agent.registerTool({ + id: 'rolldown:build-time-analysis', + title: 'Rolldown build time analysis', + description: 'Analyze where build time is spent in a Rolldown session. Returns hook breakdowns, top plugin costs, top module costs, and explanations for likely build-time bottlenecks.', + safety: 'read', + tags: [...readOnlyTags, 'performance'], + inputSchema: { + type: 'object', + properties: { + session: sessionProperty, + limit: limitProperty, + }, + additionalProperties: false, + }, + outputSchema, + examples: [ + { + args: [{ session: 'latest', limit: 5 }], + description: 'Find the highest-cost plugins and hooks in the latest build.', + }, + ], + handler: args => getAnalysis().buildTimeAnalysis(args as BuildTimeAnalysisInput), + }) + + ctx.agent.registerTool({ + id: 'rolldown:bundle-size-analysis', + title: 'Rolldown bundle size analysis', + description: 'Analyze emitted asset size, initial JavaScript, large chunks, large packages, and duplicated packages in a Rolldown build session. Use this when the user asks why output is large or which dependencies contribute most to bundle size.', + safety: 'read', + tags: [...readOnlyTags, 'bundle-size'], + inputSchema: { + type: 'object', + properties: { + session: sessionProperty, + scope: { + type: 'string', + description: 'Output scope to focus on.', + enum: ['all', 'initial', 'async', 'assets'], + }, + limit: limitProperty, + }, + additionalProperties: false, + }, + outputSchema, + examples: [ + { + args: [{ session: 'latest', scope: 'initial', limit: 5 }], + description: 'Analyze the largest contributors to initial output size.', + }, + ], + handler: args => getAnalysis().bundleSizeAnalysis(args as BundleSizeAnalysisInput), + }) + + ctx.agent.registerTool({ + id: 'rolldown:dependency-trace', + title: 'Rolldown dependency trace', + description: 'Trace why a dependency, module, package, or asset is present in a Rolldown build. Returns importer paths, matched modules, related chunks, and a concise explanation of the path that brought it into the output.', + safety: 'read', + tags: [...readOnlyTags, 'trace'], + inputSchema: { + type: 'object', + required: ['target'], + properties: { + session: sessionProperty, + target: { + type: 'object', + required: ['type', 'id'], + additionalProperties: false, + properties: { + type: { + type: 'string', + description: 'Kind of build entity to trace.', + enum: ['module', 'package', 'asset'], + }, + id: { + type: 'string', + description: 'Module id, package name/id, or asset filename to trace.', + }, + }, + }, + direction: { + type: 'string', + description: 'Trace importers, imports, or both directions. Importers answer why something is included.', + enum: ['importers', 'imports', 'both'], + }, + maxDepth: { + type: 'number', + description: 'Maximum graph depth to traverse. Defaults to 8 and is capped at 100.', + minimum: 1, + maximum: 100, + }, + limit: limitProperty, + }, + additionalProperties: false, + }, + outputSchema, + examples: [ + { + args: [{ session: 'latest', target: { type: 'package', id: 'lodash' }, direction: 'importers', maxDepth: 8, limit: 5 }], + description: 'Explain why a package is included in the latest build.', + }, + ], + handler: args => getAnalysis().dependencyTrace(args as DependencyTraceInput), + }) + + ctx.agent.registerTool({ + id: 'rolldown:build-comparison', + title: 'Rolldown build comparison', + description: 'Compare two Rolldown build sessions and explain changes in build duration, emitted output size, assets, chunks, packages, and plugin costs. Use this when the user asks why a build got slower, larger, or changed after a commit or PR.', + safety: 'read', + tags: [...readOnlyTags, 'comparison'], + inputSchema: { + type: 'object', + required: ['baseSession', 'currentSession'], + properties: { + baseSession: { + type: 'string', + description: 'Older or baseline Rolldown session id.', + }, + currentSession: { + type: 'string', + description: 'Newer or candidate Rolldown session id.', + }, + limit: limitProperty, + }, + additionalProperties: false, + }, + outputSchema, + examples: [ + { + args: [{ baseSession: 'previous-session-id', currentSession: 'current-session-id', limit: 5 }], + description: 'Compare two builds and return the most relevant changes.', + }, + ], + handler: args => getAnalysis().buildComparison(args as BuildComparisonInput), + }) +} diff --git a/packages/rolldown/src/node/agent/utils.ts b/packages/rolldown/src/node/agent/utils.ts new file mode 100644 index 000000000..1e948ddeb --- /dev/null +++ b/packages/rolldown/src/node/agent/utils.ts @@ -0,0 +1,38 @@ +import type { BuildInfo } from '../rolldown/logs-manager' + +const DEFAULT_LIMIT = 5 +const MAX_LIMIT = 200 +const DEFAULT_TRACE_DEPTH = 8 +const MAX_TRACE_DEPTH = 100 + +export function clampLimit(limit: unknown, fallback = DEFAULT_LIMIT) { + if (typeof limit !== 'number' || !Number.isFinite(limit)) + return fallback + return Math.min(MAX_LIMIT, Math.max(1, Math.floor(limit))) +} + +export function clampDepth(depth: unknown) { + if (typeof depth !== 'number' || !Number.isFinite(depth)) + return DEFAULT_TRACE_DEPTH + return Math.min(MAX_TRACE_DEPTH, Math.max(1, Math.floor(depth))) +} + +export function sortByNumberDesc(items: T[], getValue: (item: T) => number) { + return items.toSorted((a, b) => getValue(b) - getValue(a)) +} + +export function sumBy(items: Iterable, getValue: (item: T) => number) { + let total = 0 + for (const item of items) { + total += getValue(item) + } + return total +} + +export function percentage(value: number, total: number) { + return total > 0 ? value / total : 0 +} + +export function getSessionTimestamp(sessions: BuildInfo[], id: string) { + return sessions.find(session => session.id === id)?.timestamp +} diff --git a/packages/rolldown/src/node/rpc/functions/rolldown-get-session-compare-details.ts b/packages/rolldown/src/node/rpc/functions/rolldown-get-session-compare-details.ts index 51820a8ee..7d3dbcb6b 100644 --- a/packages/rolldown/src/node/rpc/functions/rolldown-get-session-compare-details.ts +++ b/packages/rolldown/src/node/rpc/functions/rolldown-get-session-compare-details.ts @@ -397,6 +397,36 @@ async function comparePlugins(previous: SessionCompareSource, current: SessionCo })) } +export async function createSessionCompareDetails( + previousReader: RolldownEventsReader, + currentReader: RolldownEventsReader, +): Promise { + const previous = createCompareSource(previousReader) + const current = createCompareSource(currentReader) + + const assets = compareAssets(previous, current) + const chunks = compareChunks(previous, current) + const packages = comparePackages(previous, current) + const plugins = await comparePlugins(previous, current) + + return { + sessionStats: { + previous: { + packages: previous.packages.length, + duplicatedPackages: previous.packages.filter(pkg => pkg.duplicated).length, + }, + current: { + packages: current.packages.length, + duplicatedPackages: current.packages.filter(pkg => pkg.duplicated).length, + }, + }, + assets, + chunks, + packages, + plugins, + } +} + export const rolldownGetSessionCompareDetails = defineRpcFunction({ name: 'vite:rolldown:get-session-compare-details', type: 'query', @@ -410,30 +440,7 @@ export const rolldownGetSessionCompareDetails = defineRpcFunction({ manager.loadAssetSession(previousSession!), manager.loadAssetSession(currentSession!), ]) - const previous = createCompareSource(previousReader) - const current = createCompareSource(currentReader) - - const assets = compareAssets(previous, current) - const chunks = compareChunks(previous, current) - const packages = comparePackages(previous, current) - const plugins = await comparePlugins(previous, current) - - return { - sessionStats: { - previous: { - packages: previous.packages.length, - duplicatedPackages: previous.packages.filter(pkg => pkg.duplicated).length, - }, - current: { - packages: current.packages.length, - duplicatedPackages: current.packages.filter(pkg => pkg.duplicated).length, - }, - }, - assets, - chunks, - packages, - plugins, - } + return createSessionCompareDetails(previousReader, currentReader) }, } }, diff --git a/packages/rolldown/tsdown.config.ts b/packages/rolldown/tsdown.config.ts index 7c3f1c2d4..27b85a097 100644 --- a/packages/rolldown/tsdown.config.ts +++ b/packages/rolldown/tsdown.config.ts @@ -2,8 +2,9 @@ import { defineConfig } from 'tsdown' export default defineConfig({ entry: { - index: 'src/index.ts', - dirs: 'src/dirs.ts', + 'index': 'src/index.ts', + 'dirs': 'src/dirs.ts', + 'node/agent': 'src/node/agent/index.ts', }, tsconfig: '../../tsconfig.base.json', target: 'esnext', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e5133897..4d8e2105a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,6 +43,9 @@ catalogs: '@devframes/hub': specifier: ^0.5.4 version: 0.5.4 + '@modelcontextprotocol/sdk': + specifier: ^1.29.0 + version: 1.29.0 actionspack: specifier: ^0.1.5 version: 0.1.5 @@ -447,7 +450,7 @@ importers: version: 3.2.4(@vitejs/devtools@0.3.3)(vite@8.1.0)(vue@3.5.38(typescript@6.0.3)) '@nuxt/eslint': specifier: catalog:devtools - version: 1.16.0(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.38)(eslint@10.5.0(jiti@2.7.0))(magicast@0.5.2)(oxc-parser@0.133.0)(rolldown@1.1.3)(typescript@6.0.3)(vite@8.1.0) + version: 1.16.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.38)(eslint@10.5.0(jiti@2.7.0))(magicast@0.5.2)(oxc-parser@0.133.0)(rolldown@1.1.3)(typescript@6.0.3)(vite@8.1.0) '@types/chrome': specifier: catalog:types version: 0.2.0 @@ -549,7 +552,7 @@ importers: version: 8.1.0(@types/node@25.0.3)(@vitejs/devtools@0.3.3)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.44.1)(tsx@4.22.4)(yaml@2.9.0) vite-plugin-inspect: specifier: catalog:devtools - version: 12.0.0-beta.3(@nuxt/kit@4.4.8(magicast@0.5.2))(typescript@6.0.3)(vite@8.1.0) + version: 12.0.0-beta.3(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(@nuxt/kit@4.4.8(magicast@0.5.2))(typescript@6.0.3)(vite@8.1.0) vite-plugin-vue-tracer: specifier: catalog:playground version: 1.4.0(vite@8.1.0)(vue@3.5.38(typescript@6.0.3)) @@ -579,7 +582,7 @@ importers: version: 4.8.4(change-case@5.4.4)(focus-trap@8.0.0)(fuse.js@7.4.2)(idb-keyval@6.2.5)(typescript@6.0.3)(vite@8.1.0(@types/node@25.0.3)(@vitejs/devtools@packages+core)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.44.1)(tsx@4.22.4)(yaml@2.9.0))(vitepress@2.0.0-alpha.17(@types/node@25.0.3)(@vitejs/devtools@packages+core)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.4.2)(idb-keyval@6.2.5)(jiti@2.7.0)(oxc-minify@0.133.0)(postcss@8.5.15)(terser@5.44.1)(tsx@4.22.4)(typescript@6.0.3)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) devframe: specifier: catalog:deps - version: 0.5.4(typescript@6.0.3) + version: 0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3) mermaid: specifier: catalog:docs version: 11.15.0 @@ -723,7 +726,10 @@ importers: dependencies: '@devframes/hub': specifier: catalog:deps - version: 0.5.4(devframe@0.5.4(typescript@6.0.3)) + version: 0.5.4(devframe@0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3)) + '@modelcontextprotocol/sdk': + specifier: catalog:deps + version: 1.29.0(zod@4.3.6) '@vitejs/devtools-kit': specifier: workspace:* version: link:../kit @@ -738,7 +744,7 @@ importers: version: 7.0.0 devframe: specifier: catalog:deps - version: 0.5.4(typescript@6.0.3) + version: 0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3) h3: specifier: catalog:deps version: 2.0.1-rc.22 @@ -814,13 +820,13 @@ importers: dependencies: '@devframes/hub': specifier: catalog:deps - version: 0.5.4(devframe@0.5.4(typescript@6.0.3)) + version: 0.5.4(devframe@0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3)) birpc: specifier: catalog:deps version: 4.0.0 devframe: specifier: catalog:deps - version: 0.5.4(typescript@6.0.3) + version: 0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3) mlly: specifier: catalog:deps version: 1.8.2 @@ -870,9 +876,6 @@ importers: d3-shape: specifier: catalog:frontend version: 3.2.0 - devframe: - specifier: catalog:deps - version: 0.5.4(typescript@6.0.3) diff: specifier: catalog:deps version: 9.0.0 @@ -990,7 +993,7 @@ importers: version: 4.0.0 devframe: specifier: catalog:deps - version: 0.5.4(typescript@6.0.3) + version: 0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3) pathe: specifier: catalog:deps version: 2.0.3 @@ -1051,7 +1054,7 @@ importers: version: 4.0.0 devframe: specifier: catalog:deps - version: 0.5.4(typescript@6.0.3) + version: 0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3) envinfo: specifier: catalog:deps version: 7.21.0 @@ -1713,6 +1716,12 @@ packages: '@floating-ui/vue@1.1.9': resolution: {integrity: sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==} + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1852,6 +1861,16 @@ packages: '@mermaid-js/parser@1.1.1': resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==} + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -4404,6 +4423,10 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -4442,6 +4465,14 @@ packages: ajv: optional: true + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv-keywords@5.1.0: resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} peerDependencies: @@ -4601,6 +4632,10 @@ packages: birpc@4.0.0: resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -4670,6 +4705,14 @@ packages: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + camelcase@7.0.1: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} @@ -4823,6 +4866,18 @@ packages: resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} engines: {node: '>= 0.6'} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -4835,12 +4890,24 @@ packages: cookie-es@3.1.1: resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + core-js-compat@3.49.0: resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -5235,6 +5302,10 @@ packages: oxc-resolver: optional: true + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -5291,9 +5362,21 @@ packages: errx@0.1.0: resolution: {integrity: sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@2.0.0: resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + es-toolkit@1.46.1: resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} @@ -5588,6 +5671,14 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -5600,6 +5691,16 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + express-rate-limit@8.5.2: + resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} @@ -5665,6 +5766,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-up-simple@1.0.1: resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} engines: {node: '>=18'} @@ -5704,6 +5809,10 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} @@ -5738,9 +5847,17 @@ packages: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-port-please@3.2.0: resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -5806,6 +5923,10 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -5841,6 +5962,10 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -5851,6 +5976,10 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hono@4.12.27: + resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==} + engines: {node: '>=16.9.0'} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} @@ -5897,6 +6026,10 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + idb-keyval@6.2.5: resolution: {integrity: sha512-eKQkTnS0relYsSOYomx8ozIbmdsQCKUdhyuIaQ2DZgKuaxtyQQMkyD/wlnQN32pO3yutN1b1L8uqwcDKaJd7/Q==} @@ -5953,6 +6086,14 @@ packages: resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} engines: {node: '>=12.22.0'} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -6018,6 +6159,9 @@ packages: resolution: {integrity: sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} @@ -6062,6 +6206,9 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6104,6 +6251,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -6405,6 +6555,10 @@ packages: engines: {node: '>= 20'} hasBin: true + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -6456,10 +6610,18 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + merge-anything@5.1.7: resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==} engines: {node: '>=12.13'} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -6697,6 +6859,10 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -6793,9 +6959,17 @@ packages: engines: {node: '>=18'} hasBin: true + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + object-deep-merge@2.0.1: resolution: {integrity: sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg==} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + obug@2.1.3: resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} engines: {node: '>=12.20.0'} @@ -6821,6 +6995,9 @@ packages: resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} engines: {node: '>= 0.8'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -6965,6 +7142,9 @@ packages: path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -6985,6 +7165,10 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -7216,6 +7400,10 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + publint@0.3.21: resolution: {integrity: sha512-OqejcnMV6E9zel2oCrUOJEiiFkGiAAni0A6ibfQNh1k9Gu5z4F+Yso8lllam7AzmV6Do0vp7u3UpZNRBwuXaHQ==} engines: {node: '>=18'} @@ -7229,6 +7417,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -7252,6 +7444,10 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + rc9@3.0.1: resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} @@ -7410,6 +7606,10 @@ packages: roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-applescript@7.1.0: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} @@ -7522,6 +7722,22 @@ packages: resolution: {integrity: sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ==} engines: {node: '>=20'} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -7912,6 +8128,10 @@ packages: resolution: {integrity: sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==} engines: {node: '>=20'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + type-level-regexp@0.1.17: resolution: {integrity: sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==} @@ -8006,6 +8226,10 @@ packages: '@unocss/webpack': optional: true + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + unplugin-utils@0.3.1: resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} engines: {node: '>=20.19.0'} @@ -8496,6 +8720,9 @@ packages: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.21.0: resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} @@ -8570,6 +8797,11 @@ packages: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -8913,10 +9145,10 @@ snapshots: '@colordx/core@5.4.3': {} - '@devframes/hub@0.5.4(devframe@0.5.4(typescript@6.0.3))': + '@devframes/hub@0.5.4(devframe@0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3))': dependencies: birpc: 4.0.0 - devframe: 0.5.4(typescript@6.0.3) + devframe: 0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3) nostics: 0.2.0 pathe: 2.0.3 perfect-debounce: 2.1.0 @@ -9124,12 +9356,12 @@ snapshots: dependencies: '@eslint/core': 1.2.1 - '@eslint/config-inspector@3.0.4(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': + '@eslint/config-inspector@3.0.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: ansis: 4.3.1 cac: 7.0.0 chokidar: 5.0.0 - devframe: 0.5.4(typescript@6.0.3) + devframe: 0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3) eslint: 10.5.0(jiti@2.7.0) jiti: 2.7.0 tinyglobby: 0.2.17 @@ -9195,6 +9427,10 @@ snapshots: - '@vue/composition-api' - vue + '@hono/node-server@1.19.14(hono@4.12.27)': + dependencies: + hono: 4.12.27 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -9376,6 +9612,28 @@ snapshots: dependencies: '@chevrotain/types': 11.1.2 + '@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.27) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.0 + express: 5.2.1 + express-rate-limit: 8.5.2(express@5.2.1) + hono: 4.12.27 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.11.0 @@ -9390,6 +9648,13 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.3 + optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: '@emnapi/core': 1.11.1 @@ -9512,7 +9777,7 @@ snapshots: which: 6.0.1 ws: 8.21.0 optionalDependencies: - '@vitejs/devtools': 0.3.3(db0@0.3.4)(idb-keyval@6.2.5)(ioredis@5.10.1)(typescript@6.0.3)(vite@8.1.0) + '@vitejs/devtools': 0.3.3(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(db0@0.3.4)(idb-keyval@6.2.5)(ioredis@5.10.1)(typescript@6.0.3)(vite@8.1.0) transitivePeerDependencies: - bufferutil - supports-color @@ -9602,9 +9867,9 @@ snapshots: - supports-color - typescript - '@nuxt/eslint@1.16.0(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.38)(eslint@10.5.0(jiti@2.7.0))(magicast@0.5.2)(oxc-parser@0.133.0)(rolldown@1.1.3)(typescript@6.0.3)(vite@8.1.0)': + '@nuxt/eslint@1.16.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.38)(eslint@10.5.0(jiti@2.7.0))(magicast@0.5.2)(oxc-parser@0.133.0)(rolldown@1.1.3)(typescript@6.0.3)(vite@8.1.0)': dependencies: - '@eslint/config-inspector': 3.0.4(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@eslint/config-inspector': 3.0.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) '@nuxt/devtools-kit': 3.2.4(magicast@0.5.2)(vite@8.1.0) '@nuxt/eslint-config': 1.16.0(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.38)(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) '@nuxt/eslint-plugin': 1.16.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) @@ -9986,7 +10251,7 @@ snapshots: dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true '@oxc-minify/binding-win32-arm64-msvc@0.133.0': @@ -10208,14 +10473,14 @@ snapshots: dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true '@oxc-parser/binding-wasm32-wasi@0.133.0': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true '@oxc-parser/binding-win32-arm64-msvc@0.129.0': @@ -11174,8 +11439,8 @@ snapshots: '@typescript-eslint/project-service@8.56.1(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3) - '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@6.0.3) + '@typescript-eslint/types': 8.62.0 debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: @@ -11466,7 +11731,7 @@ snapshots: '@unocss/eslint-plugin@66.7.2(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) '@unocss/config': 66.7.2 '@unocss/core': 66.7.2 '@unocss/rule-utils': 66.7.2 @@ -11748,11 +12013,11 @@ snapshots: '@visual-json/core': 0.4.0 vue: 3.5.38(typescript@6.0.3) - '@vitejs/devtools-kit@0.3.3(typescript@6.0.3)(vite@8.1.0)': + '@vitejs/devtools-kit@0.3.3(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3)(vite@8.1.0)': dependencies: - '@devframes/hub': 0.5.4(devframe@0.5.4(typescript@6.0.3)) + '@devframes/hub': 0.5.4(devframe@0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3)) birpc: 4.0.0 - devframe: 0.5.4(typescript@6.0.3) + devframe: 0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3) mlly: 1.8.2 nostics: 0.3.0 pathe: 2.0.3 @@ -11766,15 +12031,15 @@ snapshots: - typescript - utf-8-validate - '@vitejs/devtools-rolldown@0.3.3(db0@0.3.4)(idb-keyval@6.2.5)(ioredis@5.10.1)(typescript@6.0.3)(vite@8.1.0)(vue@3.5.38(typescript@6.0.3))': + '@vitejs/devtools-rolldown@0.3.3(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(db0@0.3.4)(idb-keyval@6.2.5)(ioredis@5.10.1)(typescript@6.0.3)(vite@8.1.0)(vue@3.5.38(typescript@6.0.3))': dependencies: '@floating-ui/dom': 1.7.6 '@rolldown/debug': 1.1.3 - '@vitejs/devtools-kit': 0.3.3(typescript@6.0.3)(vite@8.1.0) + '@vitejs/devtools-kit': 0.3.3(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3)(vite@8.1.0) birpc: 4.0.0 cac: 7.0.0 d3-shape: 3.2.0 - devframe: 0.5.4(typescript@6.0.3) + devframe: 0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3) diff: 9.0.0 get-port-please: 3.2.0 h3: 2.0.1-rc.22 @@ -11818,14 +12083,14 @@ snapshots: - vue optional: true - '@vitejs/devtools@0.3.3(db0@0.3.4)(idb-keyval@6.2.5)(ioredis@5.10.1)(typescript@6.0.3)(vite@8.1.0)': + '@vitejs/devtools@0.3.3(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(db0@0.3.4)(idb-keyval@6.2.5)(ioredis@5.10.1)(typescript@6.0.3)(vite@8.1.0)': dependencies: - '@devframes/hub': 0.5.4(devframe@0.5.4(typescript@6.0.3)) - '@vitejs/devtools-kit': 0.3.3(typescript@6.0.3)(vite@8.1.0) - '@vitejs/devtools-rolldown': 0.3.3(db0@0.3.4)(idb-keyval@6.2.5)(ioredis@5.10.1)(typescript@6.0.3)(vite@8.1.0)(vue@3.5.38(typescript@6.0.3)) + '@devframes/hub': 0.5.4(devframe@0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3)) + '@vitejs/devtools-kit': 0.3.3(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3)(vite@8.1.0) + '@vitejs/devtools-rolldown': 0.3.3(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(db0@0.3.4)(idb-keyval@6.2.5)(ioredis@5.10.1)(typescript@6.0.3)(vite@8.1.0)(vue@3.5.38(typescript@6.0.3)) birpc: 4.0.0 cac: 7.0.0 - devframe: 0.5.4(typescript@6.0.3) + devframe: 0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3) h3: 2.0.1-rc.22 mlly: 1.8.2 nostics: 0.3.0 @@ -12318,6 +12583,11 @@ snapshots: dependencies: event-target-shim: 5.0.1 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-import-attributes@1.9.5(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -12345,6 +12615,10 @@ snapshots: optionalDependencies: ajv: 8.18.0 + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + ajv-keywords@5.1.0(ajv@8.18.0): dependencies: ajv: 8.18.0 @@ -12498,6 +12772,20 @@ snapshots: birpc@4.0.0: {} + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.0.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} boxen@7.0.0: @@ -12586,6 +12874,16 @@ snapshots: cac@7.0.0: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + camelcase@7.0.1: {} caniuse-api@3.0.0: @@ -12714,6 +13012,12 @@ snapshots: content-disposition@0.5.2: {} + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.0.0: {} + convert-source-map@2.0.0: {} cookie-es@1.2.3: {} @@ -12722,12 +13026,21 @@ snapshots: cookie-es@3.1.1: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + core-js-compat@3.49.0: dependencies: browserslist: 4.28.2 core-util-is@1.0.3: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cose-base@1.0.3: dependencies: layout-base: 1.0.2 @@ -13063,7 +13376,7 @@ snapshots: devalue@5.8.1: {} - devframe@0.5.4(typescript@6.0.3): + devframe@0.5.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3): dependencies: '@valibot/to-json-schema': 1.7.0(valibot@1.4.1(typescript@6.0.3)) birpc: 4.0.0 @@ -13074,6 +13387,8 @@ snapshots: pathe: 2.0.3 valibot: 1.4.1(typescript@6.0.3) ws: 8.21.0 + optionalDependencies: + '@modelcontextprotocol/sdk': 1.29.0(zod@4.3.6) transitivePeerDependencies: - bufferutil - crossws @@ -13126,6 +13441,12 @@ snapshots: dts-resolver@3.0.0: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + duplexer@0.1.2: {} eastasianwidth@0.2.0: {} @@ -13161,8 +13482,16 @@ snapshots: errx@0.1.0: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + es-toolkit@1.46.1: {} esbuild@0.28.1: @@ -13584,6 +13913,12 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.1.0: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.0 + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -13610,6 +13945,44 @@ snapshots: expect-type@1.3.0: {} + express-rate-limit@8.5.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.2.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.3 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + exsolve@1.0.8: {} extend-shallow@2.0.1: @@ -13668,6 +14041,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up-simple@1.0.1: {} find-up@5.0.0: @@ -13706,6 +14090,8 @@ snapshots: format@0.2.2: {} + forwarded@0.2.0: {} + fraction.js@5.3.4: {} fresh@2.0.0: {} @@ -13725,8 +14111,26 @@ snapshots: get-east-asian-width@1.5.0: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-port-please@3.2.0: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + get-stream@6.0.1: {} get-stream@8.0.1: {} @@ -13789,6 +14193,8 @@ snapshots: globrex@0.1.2: {} + gopd@1.2.0: {} + graceful-fs@4.2.11: {} gray-matter@4.0.3: @@ -13827,6 +14233,8 @@ snapshots: has-flag@4.0.0: {} + has-symbols@1.1.0: {} + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -13849,6 +14257,8 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hono@4.12.27: {} + hookable@5.5.3: {} hookable@6.1.1: {} @@ -13888,6 +14298,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + idb-keyval@6.2.5: {} ieee754@1.2.1: {} @@ -13938,6 +14352,10 @@ snapshots: transitivePeerDependencies: - supports-color + ip-address@10.2.0: {} + + ipaddr.js@1.9.1: {} + iron-webcrypto@1.2.1: {} is-builtin-module@5.0.0: @@ -13981,6 +14399,8 @@ snapshots: is-port-reachable@4.0.0: {} + is-promise@4.0.0: {} + is-reference@1.2.1: dependencies: '@types/estree': 1.0.9 @@ -14019,6 +14439,8 @@ snapshots: jiti@2.7.0: {} + jose@6.2.3: {} + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -14051,6 +14473,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} @@ -14312,6 +14736,8 @@ snapshots: marked@16.4.2: {} + math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -14455,10 +14881,14 @@ snapshots: mdurl@2.0.0: {} + media-typer@1.1.0: {} + merge-anything@5.1.7: dependencies: is-what: 4.1.16 + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -14793,6 +15223,8 @@ snapshots: negotiator@0.6.4: {} + negotiator@1.0.0: {} + neo-async@2.6.2: {} nitropack@2.13.4(idb-keyval@6.2.5)(oxc-parser@0.133.0)(rolldown@1.1.3): @@ -15214,8 +15646,12 @@ snapshots: pathe: 2.0.3 tinyexec: 1.2.4 + object-assign@4.1.1: {} + object-deep-merge@2.0.1: {} + object-inspect@1.13.4: {} + obug@2.1.3: {} ofetch@1.5.1: @@ -15236,6 +15672,10 @@ snapshots: on-headers@1.1.0: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -15499,6 +15939,8 @@ snapshots: path-to-regexp@3.3.0: {} + path-to-regexp@8.4.2: {} + pathe@1.1.2: {} pathe@2.0.3: {} @@ -15511,6 +15953,8 @@ snapshots: picomatch@4.0.4: {} + pkce-challenge@5.0.1: {} + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -15728,6 +16172,11 @@ snapshots: property-information@7.1.0: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + publint@0.3.21: dependencies: '@publint/pack': 0.1.4 @@ -15739,6 +16188,11 @@ snapshots: punycode@2.3.1: {} + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + quansync@0.2.11: {} quansync@1.0.0: {} @@ -15755,6 +16209,13 @@ snapshots: range-parser@1.2.1: {} + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + rc9@3.0.1: dependencies: defu: 6.1.7 @@ -15967,6 +16428,16 @@ snapshots: points-on-curve: 0.2.0 points-on-path: 0.2.1 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + run-applescript@7.1.0: {} run-parallel@1.2.0: @@ -16114,6 +16585,34 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@3.0.7: {} @@ -16421,7 +16920,7 @@ snapshots: tree-kill: 1.2.2 unconfig-core: 7.5.0 optionalDependencies: - '@vitejs/devtools': 0.3.3(db0@0.3.4)(idb-keyval@6.2.5)(ioredis@5.10.1)(typescript@6.0.3)(vite@8.1.0) + '@vitejs/devtools': 0.3.3(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(db0@0.3.4)(idb-keyval@6.2.5)(ioredis@5.10.1)(typescript@6.0.3)(vite@8.1.0) publint: 0.3.21 tsx: 4.22.4 typescript: 6.0.3 @@ -16519,6 +17018,12 @@ snapshots: dependencies: tagged-tag: 1.0.0 + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + type-level-regexp@0.1.17: {} typescript@6.0.3: {} @@ -16663,6 +17168,8 @@ snapshots: transitivePeerDependencies: - vite + unpipe@1.0.0: {} + unplugin-utils@0.3.1: dependencies: pathe: 2.0.3 @@ -16942,9 +17449,9 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-inspect@12.0.0-beta.3(@nuxt/kit@4.4.8(magicast@0.5.2))(typescript@6.0.3)(vite@8.1.0): + vite-plugin-inspect@12.0.0-beta.3(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(@nuxt/kit@4.4.8(magicast@0.5.2))(typescript@6.0.3)(vite@8.1.0): dependencies: - '@vitejs/devtools-kit': 0.3.3(typescript@6.0.3)(vite@8.1.0) + '@vitejs/devtools-kit': 0.3.3(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(typescript@6.0.3)(vite@8.1.0) ansis: 4.3.1 error-stack-parser-es: 1.0.5 obug: 2.1.3 @@ -17005,7 +17512,7 @@ snapshots: tinyglobby: 0.2.17 optionalDependencies: '@types/node': 25.0.3 - '@vitejs/devtools': 0.3.3(db0@0.3.4)(idb-keyval@6.2.5)(ioredis@5.10.1)(typescript@6.0.3)(vite@8.1.0) + '@vitejs/devtools': 0.3.3(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(db0@0.3.4)(idb-keyval@6.2.5)(ioredis@5.10.1)(typescript@6.0.3)(vite@8.1.0) esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.7.0 @@ -17349,6 +17856,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.2 + wrappy@1.0.2: {} + ws@8.21.0: {} wsl-utils@0.1.0: @@ -17411,6 +17920,10 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.7.0 + zod-to-json-schema@3.25.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + zod@4.3.6: {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0a1bc9c3f..84225527e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -57,6 +57,7 @@ catalogs: vite: ^8.1.0 deps: '@devframes/hub': ^0.5.4 + '@modelcontextprotocol/sdk': ^1.29.0 '@rolldown/debug': ^1.1.3 actionspack: ^0.1.5 birpc: ^4.0.0 diff --git a/tsconfig.base.json b/tsconfig.base.json index 7556fca02..2e6305094 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -36,6 +36,9 @@ "@vitejs/devtools-kit": [ "./packages/kit/src/index.ts" ], + "@vitejs/devtools-rolldown/node/agent": [ + "./packages/rolldown/src/node/agent/index.ts" + ], "@vitejs/devtools-rolldown": [ "./packages/rolldown/src/index.ts" ], From ffcd589d8e6d2a2eb02c9e6a76ed3af0ef38deaf Mon Sep 17 00:00:00 2001 From: arlo Date: Wed, 15 Jul 2026 14:22:44 +0800 Subject: [PATCH 2/2] chore: update --- .../__tests__/context-capabilities.test.ts | 25 +++ .../src/node/__tests__/standalone.test.ts | 23 ++ packages/core/src/node/agents.ts | 17 -- packages/core/src/node/mcp.ts | 9 +- packages/core/src/node/standalone.ts | 5 +- packages/core/tsconfig.json | 2 +- .../src/node/agent/__tests__/plugin.test.ts | 26 +++ packages/rolldown/src/node/agent/analysis.ts | 16 +- packages/rolldown/src/node/agent/context.ts | 207 +----------------- packages/rolldown/src/node/agent/index.ts | 16 +- .../src/node/agent/modules/build-analysis.ts | 13 +- .../node/agent/modules/build-comparison.ts | 13 +- .../node/agent/modules/build-time-analysis.ts | 44 +++- .../agent/modules/bundle-size-analysis.ts | 13 +- .../node/agent/modules/dependency-trace.ts | 17 +- packages/rolldown/src/node/agent/tools.ts | 13 +- packages/rolldown/src/node/agent/types.ts | 58 +++++ packages/rolldown/src/node/agent/utils.ts | 85 +++++++ .../src/node/rpc/__tests__/utils.test.ts | 41 ++++ packages/rolldown/src/node/rpc/utils.ts | 11 +- .../node/agent.snapshot.d.ts | 7 + .../devtools-rolldown/node/agent.snapshot.js | 7 + .../devtools/cli-commands.snapshot.d.ts | 4 + .../@vitejs/devtools/cli-commands.snapshot.js | 1 + 24 files changed, 402 insertions(+), 271 deletions(-) create mode 100644 packages/core/src/node/__tests__/standalone.test.ts delete mode 100644 packages/core/src/node/agents.ts create mode 100644 packages/rolldown/src/node/agent/__tests__/plugin.test.ts create mode 100644 packages/rolldown/src/node/agent/types.ts create mode 100644 packages/rolldown/src/node/rpc/__tests__/utils.test.ts create mode 100644 test/__snapshots__/tsnapi/@vitejs/devtools-rolldown/node/agent.snapshot.d.ts create mode 100644 test/__snapshots__/tsnapi/@vitejs/devtools-rolldown/node/agent.snapshot.js diff --git a/packages/core/src/node/__tests__/context-capabilities.test.ts b/packages/core/src/node/__tests__/context-capabilities.test.ts index 83756d2c7..d4343a9eb 100644 --- a/packages/core/src/node/__tests__/context-capabilities.test.ts +++ b/packages/core/src/node/__tests__/context-capabilities.test.ts @@ -81,4 +81,29 @@ describe('createDevToolsContext capabilities gating', () => { expect(setup).toHaveBeenCalledTimes(1) }) + + it('registers agent tools contributed by configured plugins', async () => { + const plugin: Plugin = { + name: 'test-agent-provider', + devtools: { + setup(ctx) { + ctx.agent.registerTool({ + id: 'test:agent-tool', + title: 'Test agent tool', + description: 'Agent tool contributed by a configured Vite plugin.', + safety: 'read', + inputSchema: { + type: 'object', + additionalProperties: false, + }, + handler: async () => ({ result: 'ok' }), + }) + }, + }, + } + + const context = await createDevToolsContext(createConfig([plugin], 'build')) + + expect(context.agent.getTool('test:agent-tool')).toBeDefined() + }) }) diff --git a/packages/core/src/node/__tests__/standalone.test.ts b/packages/core/src/node/__tests__/standalone.test.ts new file mode 100644 index 000000000..807058ac2 --- /dev/null +++ b/packages/core/src/node/__tests__/standalone.test.ts @@ -0,0 +1,23 @@ +import type { PluginWithDevTools } from '@vitejs/devtools-kit' +import { describe, expect, it, vi } from 'vitest' +import { startStandaloneDevTools } from '../standalone' + +describe('startStandaloneDevTools', () => { + it('includes explicitly provided headless plugins in context setup', async () => { + const setup = vi.fn() + const plugin: PluginWithDevTools = { + name: 'test-headless-agent-provider', + devtools: { + setup, + }, + } + + await startStandaloneDevTools({ + cwd: process.cwd(), + builtinDevTools: false, + plugins: [plugin], + }) + + expect(setup).toHaveBeenCalledOnce() + }) +}) diff --git a/packages/core/src/node/agents.ts b/packages/core/src/node/agents.ts deleted file mode 100644 index f5ee8d12d..000000000 --- a/packages/core/src/node/agents.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' -import { rolldownAgent } from '@vitejs/devtools-rolldown/node/agent' - -export interface DevToolsAgent { - namespace: string - setup: (ctx: ViteDevToolsNodeContext) => void | Promise -} - -export const builtinAgents: readonly DevToolsAgent[] = [ - rolldownAgent, -] - -export async function registerBuiltinAgents(ctx: ViteDevToolsNodeContext) { - for (const agent of builtinAgents) { - await agent.setup(ctx) - } -} diff --git a/packages/core/src/node/mcp.ts b/packages/core/src/node/mcp.ts index 315f000a2..91d1c785b 100644 --- a/packages/core/src/node/mcp.ts +++ b/packages/core/src/node/mcp.ts @@ -99,16 +99,21 @@ function createViteDevToolsMcpDefinition(options: McpOptions) { return defineDevframe({ id: 'vite-devtools', name: 'Vite DevTools', + packageName: packageJson.name, version: packageJson.version, + description: packageJson.description, + homepage: packageJson.homepage, async setup(ctx) { + const { DevToolsRolldownAgent } = await import('@vitejs/devtools-rolldown/node/agent') const { startStandaloneDevTools } = await import('./standalone') - const { registerBuiltinAgents } = await import('./agents') const { context } = await startStandaloneDevTools({ cwd: options.root, builtinDevTools: false, + plugins: [ + DevToolsRolldownAgent(), + ], }) - await registerBuiltinAgents(context) mirrorAgentSurface(ctx, context) }, diff --git a/packages/core/src/node/standalone.ts b/packages/core/src/node/standalone.ts index 95172339c..270c17d60 100644 --- a/packages/core/src/node/standalone.ts +++ b/packages/core/src/node/standalone.ts @@ -1,5 +1,5 @@ import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' -import type { Plugin, ResolvedConfig } from 'vite' +import type { Plugin, PluginOption, ResolvedConfig } from 'vite' import process from 'node:process' import { createDevToolsContext } from './context' import { DevTools } from './plugins' @@ -11,6 +11,7 @@ export interface StandaloneDevToolsOptions { command?: 'build' | 'serve' mode?: 'development' | 'production' builtinDevTools?: boolean + plugins?: PluginOption[] } export async function startStandaloneDevTools(options: StandaloneDevToolsOptions = {}): Promise<{ @@ -22,6 +23,7 @@ export async function startStandaloneDevTools(options: StandaloneDevToolsOptions command = 'build', mode = 'production', builtinDevTools = true, + plugins = [], } = options const { resolveConfig } = await import('vite') @@ -30,6 +32,7 @@ export async function startStandaloneDevTools(options: StandaloneDevToolsOptions configFile: options.config, root: cwd, plugins: [ + ...plugins, DevTools({ builtinDevTools }), ], }, diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 8a6d5a7d1..d03db2f87 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "composite": true, + "composite": false, "lib": ["esnext", "dom"] } } diff --git a/packages/rolldown/src/node/agent/__tests__/plugin.test.ts b/packages/rolldown/src/node/agent/__tests__/plugin.test.ts new file mode 100644 index 000000000..9aa050314 --- /dev/null +++ b/packages/rolldown/src/node/agent/__tests__/plugin.test.ts @@ -0,0 +1,26 @@ +import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' +import { describe, expect, it, vi } from 'vitest' +import { DevToolsRolldownAgent } from '../index' + +describe('devToolsRolldownAgent', () => { + it('registers Rolldown tools through the standard devtools setup hook', async () => { + const registerTool = vi.fn() + const context = { + agent: { + registerTool, + }, + } as unknown as ViteDevToolsNodeContext + + const plugin = DevToolsRolldownAgent() + await plugin.devtools?.setup(context) + + expect(plugin.name).toBe('vite:devtools:rolldown-agent') + expect(registerTool.mock.calls.map(([tool]) => tool.id)).toEqual([ + 'rolldown:build-analysis', + 'rolldown:build-time-analysis', + 'rolldown:bundle-size-analysis', + 'rolldown:dependency-trace', + 'rolldown:build-comparison', + ]) + }) +}) diff --git a/packages/rolldown/src/node/agent/analysis.ts b/packages/rolldown/src/node/agent/analysis.ts index d6371532a..4335281f6 100644 --- a/packages/rolldown/src/node/agent/analysis.ts +++ b/packages/rolldown/src/node/agent/analysis.ts @@ -7,14 +7,24 @@ import { createBundleSizeAnalysis } from './modules/bundle-size-analysis' import { createDependencyTrace } from './modules/dependency-trace' export type { - AnalysisInsight, - AnalysisReport, BuildAnalysisInput, +} from './modules/build-analysis' +export type { BuildComparisonInput, +} from './modules/build-comparison' +export type { BuildTimeAnalysisInput, +} from './modules/build-time-analysis' +export type { BundleSizeAnalysisInput, +} from './modules/bundle-size-analysis' +export type { DependencyTraceInput, -} from './context' +} from './modules/dependency-trace' +export type { + AnalysisInsight, + AnalysisReport, +} from './types' export function createRolldownAnalysis(context: ViteDevToolsNodeContext) { const analysisContext = createAnalysisContext(context) diff --git a/packages/rolldown/src/node/agent/context.ts b/packages/rolldown/src/node/agent/context.ts index 5fe7e1413..a52f4b47d 100644 --- a/packages/rolldown/src/node/agent/context.ts +++ b/packages/rolldown/src/node/agent/context.ts @@ -1,87 +1,8 @@ import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' -import type { ModuleBuildMetrics, RolldownAssetInfo, RolldownChunkInfo } from '../../shared/types' import type { BuildInfo, RolldownLogsManager } from '../rolldown/logs-manager' -import { getPackageMeta } from '../rpc/functions/rolldown-get-packages' +import type { AnalysisReport } from './types' import { getLogsManager } from '../rpc/utils' -import { getSessionTimestamp, sortByNumberDesc, sumBy } from './utils' - -export const SCHEMA_VERSION = 'rolldown-agent' - -export type AnalysisSeverity = 'info' | 'low' | 'medium' | 'high' -export type AnalysisCategory = 'build-time' | 'bundle-size' | 'dependency' | 'chunking' | 'plugin' | 'asset' | 'module' -export type AnalysisUnit = 'ms' | 'bytes' | 'count' | 'percent' | 'ratio' -export type AssetSessionReader = Awaited> - -export interface EvidenceSource { - type: 'session' | 'module' | 'package' | 'plugin' | 'chunk' | 'asset' - id: string -} - -export interface EvidenceItem { - label: string - value: string | number | boolean | null - unit?: AnalysisUnit - source?: EvidenceSource -} - -export interface AnalysisInsight { - id: string - category: AnalysisCategory - severity: AnalysisSeverity - title: string - explanation: string - evidence: EvidenceItem[] - recommendations?: string[] -} - -export interface AnalysisSession { - id: string - timestamp?: number -} - -export interface AnalysisReport { - schemaVersion: typeof SCHEMA_VERSION - tool: string - session?: AnalysisSession - answer: string - summary?: object - insights?: AnalysisInsight[] - limitations?: string[] -} - -export interface BuildAnalysisInput { - session?: string - issue?: 'general' | 'slow-build' | 'large-bundle' | 'unexpected-dependency' | 'chunking' | 'dependency-duplication' - limit?: number -} - -export interface BuildTimeAnalysisInput { - session?: string - limit?: number -} - -export interface BundleSizeAnalysisInput { - session?: string - scope?: 'all' | 'initial' | 'async' | 'assets' - limit?: number -} - -export interface DependencyTraceInput { - session?: string - target: { - type: 'module' | 'package' | 'asset' - id: string - } - direction?: 'importers' | 'imports' | 'both' - maxDepth?: number - limit?: number -} - -export interface BuildComparisonInput { - baseSession: string - currentSession: string - limit?: number -} +import { createEmptyReport, createSessionNotFoundReport } from './utils' export interface ResolvedSession { id?: string @@ -90,136 +11,12 @@ export interface ResolvedSession { report?: AnalysisReport } -export interface SessionStats { - buildDuration: number - modules: number - chunks: number - assets: number - plugins: number - bundleSize: number - initialJs: number - packageGraphSupported: boolean - packages: number - duplicatedPackages: number -} - -export interface ModuleCost { - id: string - totalDuration: number - resolveDuration: number - loadDuration: number - transformDuration: number -} - export interface AgentAnalysisContext { manager: RolldownLogsManager listSessions: () => Promise resolveSession: (tool: string, requested?: string) => Promise } -export function createEmptyReport(tool: string, answer: string, limitations: string[] = []): AnalysisReport { - return { - schemaVersion: SCHEMA_VERSION, - tool, - answer, - limitations, - } -} - -export function getBuildDuration(reader: AssetSessionReader) { - return Math.max(0, reader.manager.build_end_time - reader.manager.build_start_time) -} - -export function getAssetScope(asset: RolldownAssetInfo, chunks: Map) { - if (asset.chunk_id == null) - return 'static' - return chunks.get(asset.chunk_id)?.is_initial ? 'initial' : 'async' -} - -export function getModuleTransformedSize(reader: AssetSessionReader, id: string) { - const transforms = reader.manager.modules.get(id)?.build_metrics?.transforms - return transforms?.at(-1)?.transformed_code_size ?? 0 -} - -export function getChunkSize(reader: AssetSessionReader, chunk: RolldownChunkInfo) { - const asset = reader.manager.chunkAssetMap.get(chunk.chunk_id) - if (asset) - return asset.size - - return chunk.modules.reduce((total, id) => total + getModuleTransformedSize(reader, id), 0) -} - -export function getModuleCost(id: string, metrics: ModuleBuildMetrics | undefined): ModuleCost { - const resolveDuration = sumBy(metrics?.resolve_ids ?? [], item => item.duration) - const loadDuration = sumBy(metrics?.loads ?? [], item => item.duration) - const transformDuration = sumBy(metrics?.transforms ?? [], item => item.duration) - - return { - id, - totalDuration: resolveDuration + loadDuration + transformDuration, - resolveDuration, - loadDuration, - transformDuration, - } -} - -export function getTopModuleCosts(reader: AssetSessionReader, limit: number) { - return sortByNumberDesc( - Array.from(reader.manager.modules.entries()) - .map(([id, module]) => getModuleCost(id, module.build_metrics)), - item => item.totalDuration, - ).slice(0, limit) -} - -export function createSessionStats(reader: AssetSessionReader): SessionStats { - const assets = Array.from(reader.manager.assets.values()) - const chunks = Array.from(reader.manager.chunks.values()) - const initialChunkIds = new Set(chunks.filter(chunk => chunk.is_initial).map(chunk => chunk.chunk_id)) - const packageMeta = getPackageMeta(reader) - - return { - buildDuration: getBuildDuration(reader), - modules: reader.manager.modules.size, - chunks: chunks.length, - assets: assets.length, - plugins: reader.meta?.plugins?.length ?? 0, - bundleSize: sumBy(assets, asset => asset.size), - initialJs: sumBy(assets.filter(asset => asset.chunk_id != null && initialChunkIds.has(asset.chunk_id)), asset => asset.size), - packageGraphSupported: packageMeta.isSupported, - packages: packageMeta.packages.length, - duplicatedPackages: packageMeta.packages.filter(pkg => pkg.duplicated).length, - } -} - -export function createSessionReport( - tool: string, - session: string, - sessions: BuildInfo[], - data: Omit, -): AnalysisReport { - return { - schemaVersion: SCHEMA_VERSION, - tool, - session: { - id: session, - timestamp: getSessionTimestamp(sessions, session), - }, - ...data, - } -} - -export function createSessionNotFoundReport(tool: string, session: string, sessions: BuildInfo[]): AnalysisReport { - return createEmptyReport( - tool, - `Rolldown session "${session}" was not found.`, - [ - sessions.length - ? `Available sessions: ${sessions.map(item => item.id).join(', ')}.` - : 'No Rolldown sessions were found. Run a build with Rolldown devtools output enabled first.', - ], - ) -} - export function createAnalysisContext(context: ViteDevToolsNodeContext): AgentAnalysisContext { const manager = getLogsManager(context) diff --git a/packages/rolldown/src/node/agent/index.ts b/packages/rolldown/src/node/agent/index.ts index 4cf5611b4..cbf099ceb 100644 --- a/packages/rolldown/src/node/agent/index.ts +++ b/packages/rolldown/src/node/agent/index.ts @@ -1,11 +1,13 @@ -import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' +import type { PluginWithDevTools } from '@vitejs/devtools-kit' import { registerRolldownAgentTools } from './tools' -export const rolldownAgent = { - namespace: 'rolldown', - setup(ctx: ViteDevToolsNodeContext) { - registerRolldownAgentTools(ctx) - }, -} as const +export function DevToolsRolldownAgent(): PluginWithDevTools { + return { + name: 'vite:devtools:rolldown-agent', + devtools: { + setup: registerRolldownAgentTools, + }, + } +} export { registerRolldownAgentTools } from './tools' diff --git a/packages/rolldown/src/node/agent/modules/build-analysis.ts b/packages/rolldown/src/node/agent/modules/build-analysis.ts index cdb35ec2e..c2d2da1f0 100644 --- a/packages/rolldown/src/node/agent/modules/build-analysis.ts +++ b/packages/rolldown/src/node/agent/modules/build-analysis.ts @@ -1,18 +1,23 @@ -import type { AgentAnalysisContext, AnalysisCategory, AnalysisInsight, AnalysisReport, BuildAnalysisInput } from '../context' +import type { AgentAnalysisContext } from '../context' +import type { AnalysisCategory, AnalysisInsight, AnalysisReport } from '../types' import { getPackageMeta } from '../../rpc/functions/rolldown-get-packages' import { + clampLimit, createSessionReport, createSessionStats, getAssetScope, getChunkSize, -} from '../context' -import { - clampLimit, percentage, sortByNumberDesc, sumBy, } from '../utils' +export interface BuildAnalysisInput { + session?: string + issue?: 'general' | 'slow-build' | 'large-bundle' | 'unexpected-dependency' | 'chunking' | 'dependency-duplication' + limit?: number +} + function getIssueCategory(issue: BuildAnalysisInput['issue']): AnalysisCategory | undefined { if (issue === 'slow-build') return 'build-time' diff --git a/packages/rolldown/src/node/agent/modules/build-comparison.ts b/packages/rolldown/src/node/agent/modules/build-comparison.ts index f08a7a36a..c385f7db1 100644 --- a/packages/rolldown/src/node/agent/modules/build-comparison.ts +++ b/packages/rolldown/src/node/agent/modules/build-comparison.ts @@ -1,7 +1,14 @@ -import type { AgentAnalysisContext, AnalysisInsight, AnalysisReport, BuildComparisonInput } from '../context' +import type { AgentAnalysisContext } from '../context' +import type { AnalysisInsight, AnalysisReport } from '../types' import { createSessionCompareDetails } from '../../rpc/functions/rolldown-get-session-compare-details' -import { createEmptyReport, createSessionStats, SCHEMA_VERSION } from '../context' -import { clampLimit } from '../utils' +import { SCHEMA_VERSION } from '../types' +import { clampLimit, createEmptyReport, createSessionStats } from '../utils' + +export interface BuildComparisonInput { + baseSession: string + currentSession: string + limit?: number +} export function createBuildComparison(context: AgentAnalysisContext) { const { manager, listSessions } = context diff --git a/packages/rolldown/src/node/agent/modules/build-time-analysis.ts b/packages/rolldown/src/node/agent/modules/build-time-analysis.ts index c83e18973..ffcb35120 100644 --- a/packages/rolldown/src/node/agent/modules/build-time-analysis.ts +++ b/packages/rolldown/src/node/agent/modules/build-time-analysis.ts @@ -1,16 +1,50 @@ -import type { AgentAnalysisContext, AnalysisInsight, AnalysisReport, BuildTimeAnalysisInput } from '../context' +import type { ModuleBuildMetrics } from '../../../shared/types' +import type { AgentAnalysisContext } from '../context' +import type { AnalysisInsight, AnalysisReport, AssetSessionReader } from '../types' import { + clampLimit, createSessionReport, createSessionStats, - getTopModuleCosts, -} from '../context' -import { - clampLimit, percentage, sortByNumberDesc, sumBy, } from '../utils' +export interface BuildTimeAnalysisInput { + session?: string + limit?: number +} + +interface ModuleCost { + id: string + totalDuration: number + resolveDuration: number + loadDuration: number + transformDuration: number +} + +function getModuleCost(id: string, metrics: ModuleBuildMetrics | undefined): ModuleCost { + const resolveDuration = sumBy(metrics?.resolve_ids ?? [], item => item.duration) + const loadDuration = sumBy(metrics?.loads ?? [], item => item.duration) + const transformDuration = sumBy(metrics?.transforms ?? [], item => item.duration) + + return { + id, + totalDuration: resolveDuration + loadDuration + transformDuration, + resolveDuration, + loadDuration, + transformDuration, + } +} + +function getTopModuleCosts(reader: AssetSessionReader, limit: number) { + return sortByNumberDesc( + Array.from(reader.manager.modules.entries()) + .map(([id, module]) => getModuleCost(id, module.build_metrics)), + item => item.totalDuration, + ).slice(0, limit) +} + export function createBuildTimeAnalysis(context: AgentAnalysisContext) { const { manager, resolveSession } = context diff --git a/packages/rolldown/src/node/agent/modules/bundle-size-analysis.ts b/packages/rolldown/src/node/agent/modules/bundle-size-analysis.ts index ff2559a1e..f715d5b8d 100644 --- a/packages/rolldown/src/node/agent/modules/bundle-size-analysis.ts +++ b/packages/rolldown/src/node/agent/modules/bundle-size-analysis.ts @@ -1,18 +1,23 @@ -import type { AgentAnalysisContext, AnalysisInsight, AnalysisReport, BundleSizeAnalysisInput } from '../context' +import type { AgentAnalysisContext } from '../context' +import type { AnalysisInsight, AnalysisReport } from '../types' import { getPackageMeta } from '../../rpc/functions/rolldown-get-packages' import { + clampLimit, createSessionReport, createSessionStats, getAssetScope, getChunkSize, -} from '../context' -import { - clampLimit, percentage, sortByNumberDesc, sumBy, } from '../utils' +export interface BundleSizeAnalysisInput { + session?: string + scope?: 'all' | 'initial' | 'async' | 'assets' + limit?: number +} + export function createBundleSizeAnalysis(context: AgentAnalysisContext) { const { manager, resolveSession } = context diff --git a/packages/rolldown/src/node/agent/modules/dependency-trace.ts b/packages/rolldown/src/node/agent/modules/dependency-trace.ts index 495e527ca..dbdda31b9 100644 --- a/packages/rolldown/src/node/agent/modules/dependency-trace.ts +++ b/packages/rolldown/src/node/agent/modules/dependency-trace.ts @@ -1,8 +1,19 @@ import type { PackageInfo, RolldownAssetInfo } from '../../../shared/types' -import type { AgentAnalysisContext, AnalysisReport, DependencyTraceInput } from '../context' +import type { AgentAnalysisContext } from '../context' +import type { AnalysisReport } from '../types' import { getPackageMeta } from '../../rpc/functions/rolldown-get-packages' -import { createSessionReport, getChunkSize } from '../context' -import { clampDepth, clampLimit } from '../utils' +import { clampDepth, clampLimit, createSessionReport, getChunkSize } from '../utils' + +export interface DependencyTraceInput { + session?: string + target: { + type: 'module' | 'package' | 'asset' + id: string + } + direction?: 'importers' | 'imports' | 'both' + maxDepth?: number + limit?: number +} type ModuleGraph = Map diff --git a/packages/rolldown/src/node/agent/tools.ts b/packages/rolldown/src/node/agent/tools.ts index e599ec824..b0220ff1b 100644 --- a/packages/rolldown/src/node/agent/tools.ts +++ b/packages/rolldown/src/node/agent/tools.ts @@ -28,9 +28,6 @@ const outputSchema = { } as const export function registerRolldownAgentTools(ctx: ViteDevToolsNodeContext) { - if (ctx.agent.getTool('rolldown:build-analysis')) - return - let analysis: ReturnType | undefined const getAnalysis = () => { analysis ??= createRolldownAnalysis(ctx) @@ -40,7 +37,7 @@ export function registerRolldownAgentTools(ctx: ViteDevToolsNodeContext) { ctx.agent.registerTool({ id: 'rolldown:build-analysis', title: 'Rolldown build analysis', - description: 'Analyze a Rolldown build session and return a high-level explanation with notable insights across build time, bundle size, chunks, assets, and dependencies. Use this as the default entry point when the user asks what happened in a build or reports an unclear build problem.', + description: 'Analyze a Rolldown build and explain the most important findings across build time, bundle size, chunks, assets, and dependencies. Use this as the default tool for general build questions or when the cause of a build issue is unclear.', safety: 'read', tags: readOnlyTags, inputSchema: { @@ -69,7 +66,7 @@ export function registerRolldownAgentTools(ctx: ViteDevToolsNodeContext) { ctx.agent.registerTool({ id: 'rolldown:build-time-analysis', title: 'Rolldown build time analysis', - description: 'Analyze where build time is spent in a Rolldown session. Returns hook breakdowns, top plugin costs, top module costs, and explanations for likely build-time bottlenecks.', + description: 'Show where a Rolldown build spends its time, with breakdowns by hook, plugin, and module. Use this to identify likely build-time bottlenecks.', safety: 'read', tags: [...readOnlyTags, 'performance'], inputSchema: { @@ -93,7 +90,7 @@ export function registerRolldownAgentTools(ctx: ViteDevToolsNodeContext) { ctx.agent.registerTool({ id: 'rolldown:bundle-size-analysis', title: 'Rolldown bundle size analysis', - description: 'Analyze emitted asset size, initial JavaScript, large chunks, large packages, and duplicated packages in a Rolldown build session. Use this when the user asks why output is large or which dependencies contribute most to bundle size.', + description: 'Explain what contributes to a Rolldown build\'s output size, including initial JavaScript, large chunks, assets, packages, and duplicated dependencies. Use this to investigate unexpectedly large output or identify the biggest contributors.', safety: 'read', tags: [...readOnlyTags, 'bundle-size'], inputSchema: { @@ -122,7 +119,7 @@ export function registerRolldownAgentTools(ctx: ViteDevToolsNodeContext) { ctx.agent.registerTool({ id: 'rolldown:dependency-trace', title: 'Rolldown dependency trace', - description: 'Trace why a dependency, module, package, or asset is present in a Rolldown build. Returns importer paths, matched modules, related chunks, and a concise explanation of the path that brought it into the output.', + description: 'Trace why a module, package, or asset appears in a Rolldown build. Follow its importer paths and related chunks to explain what brought it into the output.', safety: 'read', tags: [...readOnlyTags, 'trace'], inputSchema: { @@ -174,7 +171,7 @@ export function registerRolldownAgentTools(ctx: ViteDevToolsNodeContext) { ctx.agent.registerTool({ id: 'rolldown:build-comparison', title: 'Rolldown build comparison', - description: 'Compare two Rolldown build sessions and explain changes in build duration, emitted output size, assets, chunks, packages, and plugin costs. Use this when the user asks why a build got slower, larger, or changed after a commit or PR.', + description: 'Compare two Rolldown builds and explain meaningful changes in build time, output size, assets, chunks, packages, and plugin cost. Use this to investigate regressions or unexpected differences between builds.', safety: 'read', tags: [...readOnlyTags, 'comparison'], inputSchema: { diff --git a/packages/rolldown/src/node/agent/types.ts b/packages/rolldown/src/node/agent/types.ts new file mode 100644 index 000000000..8fe5ec3a3 --- /dev/null +++ b/packages/rolldown/src/node/agent/types.ts @@ -0,0 +1,58 @@ +import type { RolldownLogsManager } from '../rolldown/logs-manager' + +export const SCHEMA_VERSION = 'rolldown-agent' + +export type AnalysisSeverity = 'info' | 'low' | 'medium' | 'high' +export type AnalysisCategory = 'build-time' | 'bundle-size' | 'dependency' | 'chunking' | 'plugin' | 'asset' | 'module' +export type AnalysisUnit = 'ms' | 'bytes' | 'count' | 'percent' | 'ratio' +export type AssetSessionReader = Awaited> + +export interface EvidenceSource { + type: 'session' | 'module' | 'package' | 'plugin' | 'chunk' | 'asset' + id: string +} + +export interface EvidenceItem { + label: string + value: string | number | boolean | null + unit?: AnalysisUnit + source?: EvidenceSource +} + +export interface AnalysisInsight { + id: string + category: AnalysisCategory + severity: AnalysisSeverity + title: string + explanation: string + evidence: EvidenceItem[] + recommendations?: string[] +} + +export interface AnalysisSession { + id: string + timestamp?: number +} + +export interface AnalysisReport { + schemaVersion: typeof SCHEMA_VERSION + tool: string + session?: AnalysisSession + answer: string + summary?: object + insights?: AnalysisInsight[] + limitations?: string[] +} + +export interface SessionStats { + buildDuration: number + modules: number + chunks: number + assets: number + plugins: number + bundleSize: number + initialJs: number + packageGraphSupported: boolean + packages: number + duplicatedPackages: number +} diff --git a/packages/rolldown/src/node/agent/utils.ts b/packages/rolldown/src/node/agent/utils.ts index 1e948ddeb..775a77d98 100644 --- a/packages/rolldown/src/node/agent/utils.ts +++ b/packages/rolldown/src/node/agent/utils.ts @@ -1,4 +1,8 @@ +import type { RolldownAssetInfo, RolldownChunkInfo } from '../../shared/types' import type { BuildInfo } from '../rolldown/logs-manager' +import type { AnalysisReport, AssetSessionReader, SessionStats } from './types' +import { getPackageMeta } from '../rpc/functions/rolldown-get-packages' +import { SCHEMA_VERSION } from './types' const DEFAULT_LIMIT = 5 const MAX_LIMIT = 200 @@ -36,3 +40,84 @@ export function percentage(value: number, total: number) { export function getSessionTimestamp(sessions: BuildInfo[], id: string) { return sessions.find(session => session.id === id)?.timestamp } + +export function createEmptyReport(tool: string, answer: string, limitations: string[] = []): AnalysisReport { + return { + schemaVersion: SCHEMA_VERSION, + tool, + answer, + limitations, + } +} + +export function createSessionReport( + tool: string, + session: string, + sessions: BuildInfo[], + data: Omit, +): AnalysisReport { + return { + schemaVersion: SCHEMA_VERSION, + tool, + session: { + id: session, + timestamp: getSessionTimestamp(sessions, session), + }, + ...data, + } +} + +export function createSessionNotFoundReport(tool: string, session: string, sessions: BuildInfo[]): AnalysisReport { + return createEmptyReport( + tool, + `Rolldown session "${session}" was not found.`, + [ + sessions.length + ? `Available sessions: ${sessions.map(item => item.id).join(', ')}.` + : 'No Rolldown sessions were found. Run a build with Rolldown devtools output enabled first.', + ], + ) +} + +export function getBuildDuration(reader: AssetSessionReader) { + return Math.max(0, reader.manager.build_end_time - reader.manager.build_start_time) +} + +export function getAssetScope(asset: RolldownAssetInfo, chunks: Map) { + if (asset.chunk_id == null) + return 'static' + return chunks.get(asset.chunk_id)?.is_initial ? 'initial' : 'async' +} + +export function getModuleTransformedSize(reader: AssetSessionReader, id: string) { + const transforms = reader.manager.modules.get(id)?.build_metrics?.transforms + return transforms?.at(-1)?.transformed_code_size ?? 0 +} + +export function getChunkSize(reader: AssetSessionReader, chunk: RolldownChunkInfo) { + const asset = reader.manager.chunkAssetMap.get(chunk.chunk_id) + if (asset) + return asset.size + + return chunk.modules.reduce((total, id) => total + getModuleTransformedSize(reader, id), 0) +} + +export function createSessionStats(reader: AssetSessionReader): SessionStats { + const assets = Array.from(reader.manager.assets.values()) + const chunks = Array.from(reader.manager.chunks.values()) + const initialChunkIds = new Set(chunks.filter(chunk => chunk.is_initial).map(chunk => chunk.chunk_id)) + const packageMeta = getPackageMeta(reader) + + return { + buildDuration: getBuildDuration(reader), + modules: reader.manager.modules.size, + chunks: chunks.length, + assets: assets.length, + plugins: reader.meta?.plugins?.length ?? 0, + bundleSize: sumBy(assets, asset => asset.size), + initialJs: sumBy(assets.filter(asset => asset.chunk_id != null && initialChunkIds.has(asset.chunk_id)), asset => asset.size), + packageGraphSupported: packageMeta.isSupported, + packages: packageMeta.packages.length, + duplicatedPackages: packageMeta.packages.filter(pkg => pkg.duplicated).length, + } +} diff --git a/packages/rolldown/src/node/rpc/__tests__/utils.test.ts b/packages/rolldown/src/node/rpc/__tests__/utils.test.ts new file mode 100644 index 000000000..85799d2e7 --- /dev/null +++ b/packages/rolldown/src/node/rpc/__tests__/utils.test.ts @@ -0,0 +1,41 @@ +import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' +import { mkdirSync, mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import process from 'node:process' +import { join } from 'pathe' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { diagnostics } from '../../diagnostics' +import { getLogsManager } from '../utils' + +vi.mock('../../diagnostics', () => ({ + diagnostics: { + RDDT0001: vi.fn(), + }, +})) + +describe('getLogsManager', () => { + const tempDirs: string[] = [] + + afterEach(() => { + vi.restoreAllMocks() + for (const dir of tempDirs.splice(0)) + rmSync(dir, { recursive: true, force: true }) + }) + + it('does not fall back to logs from the process working directory', () => { + const tempDir = mkdtempSync(join(tmpdir(), 'vite-devtools-rolldown-root-')) + tempDirs.push(tempDir) + + const targetRoot = join(tempDir, 'target') + const processRoot = join(tempDir, 'launcher') + mkdirSync(targetRoot, { recursive: true }) + mkdirSync(join(processRoot, 'node_modules', '.rolldown'), { recursive: true }) + vi.spyOn(process, 'cwd').mockReturnValue(processRoot) + + const context = { cwd: targetRoot } as ViteDevToolsNodeContext + const manager = getLogsManager(context) + + expect(manager.dir).toBe(join(targetRoot, 'node_modules', '.rolldown')) + expect(diagnostics.RDDT0001).toHaveBeenCalledOnce() + }) +}) diff --git a/packages/rolldown/src/node/rpc/utils.ts b/packages/rolldown/src/node/rpc/utils.ts index cc94f8b26..2abad3fe2 100644 --- a/packages/rolldown/src/node/rpc/utils.ts +++ b/packages/rolldown/src/node/rpc/utils.ts @@ -1,6 +1,5 @@ import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' import { existsSync } from 'node:fs' -import process from 'node:process' import { join } from 'pathe' import { diagnostics } from '../diagnostics' import { RolldownLogsManager } from '../rolldown/logs-manager' @@ -10,15 +9,11 @@ const weakMap = new WeakMap() export function getLogsManager(context: ViteDevToolsNodeContext): RolldownLogsManager { let manager = weakMap.get(context)! if (!manager) { - const dirs = [ - join(context.cwd, 'node_modules', '.rolldown'), - join(process.cwd(), 'node_modules', '.rolldown'), - ] - const dir = dirs.find(dir => existsSync(dir)) - if (!dir) { + const dir = join(context.cwd, 'node_modules', '.rolldown') + if (!existsSync(dir)) { diagnostics.RDDT0001() } - manager = new RolldownLogsManager(dir ?? dirs[0]!) + manager = new RolldownLogsManager(dir) } return manager } diff --git a/test/__snapshots__/tsnapi/@vitejs/devtools-rolldown/node/agent.snapshot.d.ts b/test/__snapshots__/tsnapi/@vitejs/devtools-rolldown/node/agent.snapshot.d.ts new file mode 100644 index 000000000..f66291436 --- /dev/null +++ b/test/__snapshots__/tsnapi/@vitejs/devtools-rolldown/node/agent.snapshot.d.ts @@ -0,0 +1,7 @@ +/** + * Generated by tsnapi — public API snapshot of `@vitejs/devtools-rolldown/node/agent` + */ +// #region Functions +export declare function DevToolsRolldownAgent(): PluginWithDevTools; +export declare function registerRolldownAgentTools(_: ViteDevToolsNodeContext): void; +// #endregion \ No newline at end of file diff --git a/test/__snapshots__/tsnapi/@vitejs/devtools-rolldown/node/agent.snapshot.js b/test/__snapshots__/tsnapi/@vitejs/devtools-rolldown/node/agent.snapshot.js new file mode 100644 index 000000000..3328d2f9e --- /dev/null +++ b/test/__snapshots__/tsnapi/@vitejs/devtools-rolldown/node/agent.snapshot.js @@ -0,0 +1,7 @@ +/** + * Generated by tsnapi — public API snapshot of `@vitejs/devtools-rolldown/node/agent` + */ +// #region Functions +export function DevToolsRolldownAgent() {} +export function registerRolldownAgentTools(_) {} +// #endregion \ No newline at end of file diff --git a/test/__snapshots__/tsnapi/@vitejs/devtools/cli-commands.snapshot.d.ts b/test/__snapshots__/tsnapi/@vitejs/devtools/cli-commands.snapshot.d.ts index 5fc89c50b..777ea986d 100644 --- a/test/__snapshots__/tsnapi/@vitejs/devtools/cli-commands.snapshot.d.ts +++ b/test/__snapshots__/tsnapi/@vitejs/devtools/cli-commands.snapshot.d.ts @@ -8,6 +8,9 @@ export interface BuildOptions { outDir: string; base: string; } +export interface McpOptions { + root?: string; +} export interface StartOptions { root?: string; config?: string; @@ -19,5 +22,6 @@ export interface StartOptions { // #region Functions export declare function build(_: BuildOptions): Promise; +export declare function mcp(_: McpOptions): Promise; export declare function start(_: StartOptions): Promise; // #endregion \ No newline at end of file diff --git a/test/__snapshots__/tsnapi/@vitejs/devtools/cli-commands.snapshot.js b/test/__snapshots__/tsnapi/@vitejs/devtools/cli-commands.snapshot.js index 0c44dafb4..efd591622 100644 --- a/test/__snapshots__/tsnapi/@vitejs/devtools/cli-commands.snapshot.js +++ b/test/__snapshots__/tsnapi/@vitejs/devtools/cli-commands.snapshot.js @@ -3,5 +3,6 @@ */ // #region Functions export async function build(_) {} +export async function mcp(_) {} export async function start(_) {} // #endregion \ No newline at end of file