Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/internal': r('core/src/internal.ts'),
'@vitejs/devtools/client/inject': r('core/src/client/inject/index.ts'),
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"@devframes/plugin-inspect": "catalog:deps",
"@devframes/plugin-messages": "catalog:deps",
"@devframes/plugin-terminals": "catalog:deps",
"@modelcontextprotocol/sdk": "catalog:deps",
"@vitejs/devtools-kit": "workspace:*",
"@vitejs/devtools-rolldown": "workspace:*",
"birpc": "catalog:deps",
Expand Down
25 changes: 25 additions & 0 deletions packages/core/src/node/__tests__/context-capabilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
})
23 changes: 23 additions & 0 deletions packages/core/src/node/__tests__/standalone.test.ts
Original file line number Diff line number Diff line change
@@ -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()
})
})
9 changes: 9 additions & 0 deletions packages/core/src/node/cli-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
8 changes: 8 additions & 0 deletions packages/core/src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ cli
return await build(options)
})

cli
.command('mcp', 'Start Vite DevTools MCP server over stdio')
.option('--root <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>', 'Root directory', { default: process.cwd() })
Expand Down
138 changes: 138 additions & 0 deletions packages/core/src/node/mcp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* 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<void>((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',
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 { context } = await startStandaloneDevTools({
cwd: options.root,
builtinDevTools: false,
plugins: [
DevToolsRolldownAgent(),
],
})

mirrorAgentSurface(ctx, context)
},
})
}

export async function startMcpServer(options: McpOptions = {}) {
const restoreConsole = redirectConsoleOutputToStderr()
let handle: Awaited<ReturnType<typeof createMcpServer>> | undefined
try {
handle = await createMcpServer(createViteDevToolsMcpDefinition(options), {
transport: 'stdio',
serverName: 'vite-devtools',
serverVersion: packageJson.version,
})
await waitForStdioClose()
}
finally {
await handle?.stop()
restoreConsole()
}
}
9 changes: 7 additions & 2 deletions packages/core/src/node/standalone.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -10,6 +10,8 @@ export interface StandaloneDevToolsOptions {
config?: string
command?: 'build' | 'serve'
mode?: 'development' | 'production'
builtinDevTools?: boolean
plugins?: PluginOption[]
}

export async function startStandaloneDevTools(options: StandaloneDevToolsOptions = {}): Promise<{
Expand All @@ -20,6 +22,8 @@ export async function startStandaloneDevTools(options: StandaloneDevToolsOptions
cwd = process.cwd(),
command = 'build',
mode = 'production',
builtinDevTools = true,
plugins = [],
} = options

const { resolveConfig } = await import('vite')
Expand All @@ -28,7 +32,8 @@ export async function startStandaloneDevTools(options: StandaloneDevToolsOptions
configFile: options.config,
root: cwd,
plugins: [
DevTools(),
...plugins,
DevTools({ builtinDevTools }),
],
},
command,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"composite": false,
"lib": ["esnext", "dom"]
}
}
1 change: 1 addition & 0 deletions packages/rolldown/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
26 changes: 26 additions & 0 deletions packages/rolldown/src/node/agent/__tests__/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -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',
])
})
})
39 changes: 39 additions & 0 deletions packages/rolldown/src/node/agent/analysis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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 {
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 './modules/dependency-trace'
export type {
AnalysisInsight,
AnalysisReport,
} from './types'

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),
}
}
Loading
Loading