diff --git a/packages/vite/package.json b/packages/vite/package.json index 18618212f..b44b9fa62 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -44,12 +44,14 @@ "perfect-debounce": "catalog:deps" }, "devDependencies": { + "@dagrejs/dagre": "catalog:frontend", "@floating-ui/dom": "catalog:frontend", "@types/d3-hierarchy": "catalog:types", "@types/envinfo": "catalog:types", "@types/splitpanes": "catalog:types", "@unocss/nuxt": "catalog:build", "@vitejs/devtools-ui": "workspace:*", + "@vue-flow/core": "catalog:frontend", "@vueuse/components": "catalog:frontend", "@vueuse/core": "catalog:frontend", "@vueuse/nuxt": "catalog:build", diff --git a/packages/vite/src/app/app.vue b/packages/vite/src/app/app.vue index 764e04c2b..eb2abeb3a 100644 --- a/packages/vite/src/app/app.vue +++ b/packages/vite/src/app/app.vue @@ -24,6 +24,11 @@ useSideNav(() => { icon: 'i-ph-house-duotone', to: '/home', }, + { + title: 'HMR Inspector', + icon: 'i-ph-lightning-duotone', + to: '/hmr', + }, { title: 'Modules Graph', icon: 'i-ph-graph-duotone', diff --git a/packages/vite/src/app/components/HmrPropagationGraph.vue b/packages/vite/src/app/components/HmrPropagationGraph.vue new file mode 100644 index 000000000..791241a87 --- /dev/null +++ b/packages/vite/src/app/components/HmrPropagationGraph.vue @@ -0,0 +1,199 @@ + + + + + diff --git a/packages/vite/src/app/pages/hmr.vue b/packages/vite/src/app/pages/hmr.vue new file mode 100644 index 000000000..690c245fc --- /dev/null +++ b/packages/vite/src/app/pages/hmr.vue @@ -0,0 +1,575 @@ + + + + + diff --git a/packages/vite/src/node/__tests__/inspect-plugin-notify.test.ts b/packages/vite/src/node/__tests__/inspect-plugin-notify.test.ts index 4bcd491c0..e50bf8086 100644 --- a/packages/vite/src/node/__tests__/inspect-plugin-notify.test.ts +++ b/packages/vite/src/node/__tests__/inspect-plugin-notify.test.ts @@ -1,5 +1,5 @@ import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' -import type { ResolvedConfig, ViteDevServer } from 'vite' +import type { EnvironmentModuleNode, ResolvedConfig, ViteDevServer } from 'vite' import type { ViteInspectModuleUpdatedState } from '../rpc/inspect-module-updated' import { EventEmitter } from 'node:events' import { afterEach, describe, expect, it, vi } from 'vitest' @@ -18,6 +18,45 @@ describe('vite:inspect:module-updated notifications', () => { cleanupCtx = undefined }) + it('registers HMR RPCs backed by the inspector hot-update hook', async () => { + const plugin = DevToolsViteInspect() + await Reflect.apply(plugin.configResolved as (config: ResolvedConfig) => void | Promise, {}, [{ + root: process.cwd(), + command: 'build', + } as ResolvedConfig]) + const register = vi.fn() + const ctx = { + diagnostics: { register: vi.fn() }, + rpc: { register }, + } as unknown as ViteDevToolsNodeContext + await plugin.devtools!.setup!(ctx) + + const id = `${process.cwd()}/src/hmr-example.ts` + await Reflect.apply(plugin.hotUpdate as (options: unknown) => unknown, {}, [{ + type: 'update', + file: id, + timestamp: 1000, + modules: [{ + id, + url: '/src/hmr-example.ts', + type: 'js', + isSelfAccepting: true, + importers: new Set(), + acceptedHmrDeps: new Set(), + } as EnvironmentModuleNode], + }]) + + const definitions = register.mock.calls.map(([definition]) => definition) + const updates = definitions.find(definition => definition.name === 'vite:hmr-updates').setup(ctx) + const clear = definitions.find(definition => definition.name === 'vite:hmr-clear').setup(ctx) + expect(await updates.handler()).toMatchObject([{ + timestamp: 1000, + boundaries: ['src/hmr-example.ts'], + }]) + await clear.handler() + expect(await updates.handler()).toEqual([]) + }) + it('notifies subscribers on watcher events and requests', async () => { const plugin = DevToolsViteInspect() const config = { diff --git a/packages/vite/src/node/hmr/__tests__/plugin.test.ts b/packages/vite/src/node/hmr/__tests__/plugin.test.ts new file mode 100644 index 000000000..1ff206673 --- /dev/null +++ b/packages/vite/src/node/hmr/__tests__/plugin.test.ts @@ -0,0 +1,94 @@ +import type { EnvironmentModuleNode, Plugin, ResolvedConfig } from 'vite' +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'pathe' +import { afterEach, beforeEach, describe, expect, it } from 'vitest' +import { createHmrTrackerPlugin } from '../plugin' +import { createHmrTracker } from '../tracker' + +function moduleNode(id: string, selfAccepting = false): EnvironmentModuleNode { + return { + id, + url: id, + type: 'js', + isSelfAccepting: selfAccepting, + importers: new Set(), + acceptedHmrDeps: new Set(), + acceptedHmrExports: null, + } as EnvironmentModuleNode +} + +function hook any>(value: T | { handler: T } | undefined): T { + return typeof value === 'function' ? value : value!.handler +} + +describe('hMR tracking plugin', () => { + let workspace: string + let tracker: ReturnType + let plugin: Plugin + + beforeEach(() => { + workspace = mkdtempSync(join(tmpdir(), 'devtools-hmr-')) + writeFileSync(join(workspace, 'pnpm-workspace.yaml'), 'packages:\n - apps/*\n') + mkdirSync(join(workspace, 'apps/client'), { recursive: true }) + tracker = createHmrTracker() + plugin = createHmrTrackerPlugin(tracker) + // Vite invokes configuration and HMR hooks with separate context objects. + Reflect.apply(hook(plugin.configResolved), {}, [{ root: join(workspace, 'apps/client') } as ResolvedConfig]) + }) + + afterEach(() => rmSync(workspace, { recursive: true, force: true })) + + function update(modules: EnvironmentModuleNode[]) { + Reflect.apply(hook(plugin.hotUpdate), {}, [{ + type: 'update', + file: modules[0]!.id, + modules, + timestamp: 1000, + }]) + return tracker.getUpdates()[0]! + } + + it('records paths relative to the workspace, across hook contexts', () => { + const source = moduleNode(join(workspace, 'apps/client/src/main.ts'), true) + const result = update([source]) + expect(result.files).toEqual(['apps/client/src/main.ts']) + expect(result.modules).toEqual(result.files) + expect(result.boundaries).toEqual(result.files) + }) + + it('marks a self-accepting importer as a boundary and stops there', () => { + const source = moduleNode(join(workspace, 'dep.ts')) + const boundary = moduleNode(join(workspace, 'component.vue'), true) + source.importers.add(boundary) + boundary.importers.add(moduleNode(join(workspace, 'app.ts'))) + const result = update([source]) + expect(result.boundaries).toEqual(['component.vue']) + expect(result.graph.nodes.map(({ id, type }) => ({ id, type }))).toEqual([ + { id: 'dep.ts', type: 'source' }, + { id: 'component.vue', type: 'boundary' }, + ]) + expect(result.graph.edges).toEqual([{ from: 'component.vue', to: 'dep.ts' }]) + }) + + it('preserves the boundary role when an importer is also an updated module', () => { + const source = moduleNode(join(workspace, 'dep.ts')) + const boundary = moduleNode(join(workspace, 'component.vue'), true) + source.importers.add(boundary) + const result = update([source, boundary]) + expect(result.graph.nodes.find(node => node.id === 'component.vue')?.type).toBe('boundary') + }) + + it('stops at a dependency-accepting boundary', () => { + const source = moduleNode(join(workspace, 'dep.ts')) + const boundary = moduleNode(join(workspace, 'accept.ts')) + boundary.acceptedHmrDeps.add(source) + source.importers.add(boundary) + const result = update([source]) + expect(result.boundaries).toEqual(['accept.ts']) + expect(result.graph.nodes.find(node => node.id === 'accept.ts')).toMatchObject({ + type: 'boundary', + acceptedDeps: ['dep.ts'], + }) + }) +}) diff --git a/packages/vite/src/node/hmr/__tests__/tracker.test.ts b/packages/vite/src/node/hmr/__tests__/tracker.test.ts new file mode 100644 index 000000000..4fdb4ff8b --- /dev/null +++ b/packages/vite/src/node/hmr/__tests__/tracker.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'vitest' +import { createHmrTracker } from '../tracker' + +function update(overrides = {}) { + return { + timestamp: 1000, + type: 'update' as const, + files: ['a.ts'], + modules: [], + boundaries: [], + graph: { nodes: [], edges: [] }, + ...overrides, + } +} + +describe('createHmrTracker', () => { + it('should store updates newest first', () => { + const tracker = createHmrTracker() + + tracker.record(update({ files: ['a.ts'] })) + tracker.record(update({ timestamp: 2000, files: ['b.ts'], boundaries: ['b.ts'] })) + + const updates = tracker.getUpdates() + expect(updates).toHaveLength(2) + expect(updates[0]?.files[0]).toBe('b.ts') + expect(updates[0]?.boundaries).toEqual(['b.ts']) + expect(updates[1]?.files[0]).toBe('a.ts') + }) + + it('should evict oldest entries when exceeding max history', () => { + const tracker = createHmrTracker() + + for (let i = 0; i < 210; i++) { + tracker.record(update({ timestamp: i, files: [`file-${i}.ts`] })) + } + + const updates = tracker.getUpdates() + expect(updates).toHaveLength(200) + expect(updates[0]?.files[0]).toBe('file-209.ts') + expect(updates[199]?.files[0]).toBe('file-10.ts') + }) + + it('should clear all updates', () => { + const tracker = createHmrTracker() + + tracker.record(update()) + tracker.record(update({ timestamp: 2000, files: ['b.ts'] })) + + tracker.clear() + expect(tracker.getUpdates()).toHaveLength(0) + }) +}) diff --git a/packages/vite/src/node/hmr/plugin.ts b/packages/vite/src/node/hmr/plugin.ts new file mode 100644 index 000000000..ad6ea4252 --- /dev/null +++ b/packages/vite/src/node/hmr/plugin.ts @@ -0,0 +1,93 @@ +import type { EnvironmentModuleNode, Plugin } from 'vite' +import type { HmrGraphEdge, HmrGraphNode } from '../../shared/types' +import type { HmrTracker } from './tracker' +import { isAbsolute, relative } from 'pathe' +import { searchForWorkspaceRoot } from 'vite' + +export function createHmrTrackerPlugin(tracker: HmrTracker) { + let root: string + return { + name: 'vite:devtools:hmr-tracker', + configResolved(config) { + root = searchForWorkspaceRoot(config.root) + }, + hotUpdate({ type: changeType, file, modules, timestamp }) { + if (modules.length === 0) + return + const rel = (p: string) => isAbsolute(p) ? relative(root, p) : p + const decode = (s: string) => { + try { + return decodeURIComponent(s) + } + catch { + return s + } + } + const modId = (m: EnvironmentModuleNode) => rel(decode(m.id ?? m.url)) + + // Walk the module graph to build propagation path + const boundaries = new Set() + const graphNodes = new Map() + const graphEdges: HmrGraphEdge[] = [] + const visited = new Set() + + function nodeData(mod: EnvironmentModuleNode, role: HmrGraphNode['type']): HmrGraphNode { + const id = modId(mod) + const node: HmrGraphNode = { id, type: role, moduleType: mod.type, selfAccepting: !!mod.isSelfAccepting, importersCount: mod.importers.size } + const deps = [...mod.acceptedHmrDeps].map(d => modId(d)) + if (deps.length) + node.acceptedDeps = deps + if (mod.acceptedHmrExports !== null && mod.acceptedHmrExports !== undefined) + node.acceptedExports = [...mod.acceptedHmrExports] + return node + } + + function walk(mod: EnvironmentModuleNode) { + if (visited.has(mod)) + return + visited.add(mod) + + const id = modId(mod) + + if (mod.isSelfAccepting) { + boundaries.add(id) + graphNodes.set(id, nodeData(mod, 'boundary')) + return + } + + for (const importer of mod.importers) { + const importerId = modId(importer) + graphEdges.push({ from: importerId, to: id }) + + if (importer.acceptedHmrDeps.has(mod)) { + boundaries.add(importerId) + graphNodes.set(importerId, nodeData(importer, 'boundary')) + } + else { + if (!graphNodes.has(importerId)) + graphNodes.set(importerId, nodeData(importer, 'intermediate')) + walk(importer) + } + } + } + + const relFile = rel(file) + for (const mod of modules) { + graphNodes.set(modId(mod), nodeData(mod, 'source')) + } + for (const mod of modules) { + walk(mod) + } + + tracker.record({ + timestamp, + type: 'update', + changeType, + files: [relFile], + modules: modules.map(m => modId(m)), + boundaries: [...boundaries], + graph: { nodes: [...graphNodes.values()], edges: graphEdges }, + }) + }, + } satisfies Plugin +} diff --git a/packages/vite/src/node/hmr/tracker.ts b/packages/vite/src/node/hmr/tracker.ts new file mode 100644 index 000000000..acb12848f --- /dev/null +++ b/packages/vite/src/node/hmr/tracker.ts @@ -0,0 +1,37 @@ +import type { HmrUpdate } from '../../shared/types' + +/** Maximum number of HMR events retained in the circular buffer. */ +const MAX_HISTORY = 200 + +/** + * Creates an in-memory tracker that records HMR events from Vite's + * `hotUpdate` hook and exposes them to the client via RPC. + */ +export function createHmrTracker() { + const updates: HmrUpdate[] = [] + let counter = 0 + + /** Prepend a new update to the history, evicting the oldest entry if full. */ + function record(update: Omit) { + const entry: HmrUpdate = { ...update, id: String(++counter) } + updates.unshift(entry) + if (updates.length > MAX_HISTORY) { + updates.length = MAX_HISTORY + } + return entry + } + + /** Return all recorded updates, newest first. */ + function getUpdates() { + return updates + } + + /** Discard all recorded updates. */ + function clear() { + updates.length = 0 + } + + return { record, getUpdates, clear } +} + +export type HmrTracker = ReturnType diff --git a/packages/vite/src/node/inspect/plugin.ts b/packages/vite/src/node/inspect/plugin.ts index 32ba60965..95546ddd0 100644 --- a/packages/vite/src/node/inspect/plugin.ts +++ b/packages/vite/src/node/inspect/plugin.ts @@ -5,6 +5,8 @@ import { rmSync } from 'node:fs' import { join } from 'node:path' import { debounce } from 'perfect-debounce' import { diagnostics } from '../diagnostics' +import { createHmrTrackerPlugin } from '../hmr/plugin' +import { createHmrTracker } from '../hmr/tracker' import { inspectRpcFunctions, viteRpcFunctions } from '../rpc' import { getViteInspectModuleUpdatedState, @@ -20,6 +22,8 @@ import { } from './server' export function DevToolsViteInspect(): PluginWithDevTools { + const hmrTracker = createHmrTracker() + const hmrPlugin = createHmrTrackerPlugin(hmrTracker) let inspectContext: ViteInspectContext | undefined let inspectContextPromise: Promise | undefined let closingInspectContext: Promise | undefined @@ -86,6 +90,7 @@ export function DevToolsViteInspect(): PluginWithDevTools { devtools: { async setup(ctx) { + ;(ctx as any).__hmrTracker = hmrTracker ctx.diagnostics.register(diagnostics) for (const fn of viteRpcFunctions) @@ -116,6 +121,7 @@ export function DevToolsViteInspect(): PluginWithDevTools { }, async configResolved(config) { + hmrPlugin.configResolved.call(this, config) const ctx = await ensureInspectContext(config) if (!ctx) return @@ -181,7 +187,9 @@ export function DevToolsViteInspect(): PluginWithDevTools { } }, - hotUpdate({ modules }) { + hotUpdate(options) { + hmrPlugin.hotUpdate.call(this, options) + const { modules } = options if (!inspectContext) return if (!inspectContext.getEnvContext(this.environment)) diff --git a/packages/vite/src/node/rpc/functions/vite-hmr-clear.ts b/packages/vite/src/node/rpc/functions/vite-hmr-clear.ts new file mode 100644 index 000000000..154db8c45 --- /dev/null +++ b/packages/vite/src/node/rpc/functions/vite-hmr-clear.ts @@ -0,0 +1,17 @@ +import type { HmrTracker } from '../../hmr/tracker' +import { defineRpcFunction } from '@vitejs/devtools-kit' + +/** Clears the recorded HMR update history. */ +export const viteHmrClear = defineRpcFunction({ + name: 'vite:hmr-clear', + type: 'action', + jsonSerializable: true, + setup: (context) => { + const tracker: HmrTracker | undefined = (context as any).__hmrTracker + return { + handler: async () => { + tracker?.clear() + }, + } + }, +}) diff --git a/packages/vite/src/node/rpc/functions/vite-hmr-updates.ts b/packages/vite/src/node/rpc/functions/vite-hmr-updates.ts new file mode 100644 index 000000000..40f4d479a --- /dev/null +++ b/packages/vite/src/node/rpc/functions/vite-hmr-updates.ts @@ -0,0 +1,17 @@ +import type { HmrTracker } from '../../hmr/tracker' +import { defineRpcFunction } from '@vitejs/devtools-kit' + +/** Returns the current list of recorded HMR updates. */ +export const viteHmrUpdates = defineRpcFunction({ + name: 'vite:hmr-updates', + type: 'query', + jsonSerializable: true, + setup: (context) => { + const tracker: HmrTracker | undefined = (context as any).__hmrTracker + return { + handler: async () => { + return tracker?.getUpdates() ?? [] + }, + } + }, +}) diff --git a/packages/vite/src/node/rpc/index.ts b/packages/vite/src/node/rpc/index.ts index a45e69f75..dee86819f 100644 --- a/packages/vite/src/node/rpc/index.ts +++ b/packages/vite/src/node/rpc/index.ts @@ -8,6 +8,8 @@ import { viteGetModulesList } from './functions/vite-get-modules-list' import { viteGetPluginDetails } from './functions/vite-get-plugin-details' import { viteGetPluginMetrics } from './functions/vite-get-plugin-metrics' import { viteGetServerMetrics } from './functions/vite-get-server-metrics' +import { viteHmrClear } from './functions/vite-hmr-clear' +import { viteHmrUpdates } from './functions/vite-hmr-updates' import { viteMetaInfo } from './functions/vite-meta-info' import { viteResolveId } from './functions/vite-resolve-id' import '@vitejs/devtools-kit' @@ -20,6 +22,8 @@ export { export const viteRpcFunctions = [ viteMetaInfo, viteEnvInfo, + viteHmrUpdates, + viteHmrClear, ] as const export const inspectRpcFunctions = [ diff --git a/packages/vite/src/shared/types.ts b/packages/vite/src/shared/types.ts new file mode 100644 index 000000000..be8e0af35 --- /dev/null +++ b/packages/vite/src/shared/types.ts @@ -0,0 +1,44 @@ +/** A node in the HMR propagation graph. */ +export interface HmrGraphNode { + /** Module ID (relative to project root). */ + id: string + /** Role of the node in the propagation. */ + type: 'source' | 'intermediate' | 'boundary' + /** Vite module type. */ + moduleType?: 'js' | 'css' | 'asset' + /** Whether this module accepts its own updates. */ + selfAccepting?: boolean + /** Module IDs this boundary accepts as HMR deps. */ + acceptedDeps?: string[] + /** Exports accepted for partial HMR (null = full accept). */ + acceptedExports?: string[] | null + /** Number of modules that import this one. */ + importersCount?: number +} + +/** A directed edge in the HMR propagation graph. */ +export interface HmrGraphEdge { + /** Importing module (relative to project root). */ + from: string + /** Imported module (relative to project root). */ + to: string +} + +export interface HmrUpdate { + /** Auto-incremented identifier, unique within the current session. */ + id: string + /** Unix timestamp (ms) when the update was received. */ + timestamp: number + /** Whether the change was a hot module replacement or a full page reload. */ + type: 'update' | 'full-reload' + /** How the file changed on disk. */ + changeType?: 'create' | 'update' | 'delete' + /** File paths (relative to project root) that triggered the update. */ + files: string[] + /** Module IDs (relative to project root) invalidated by the change. */ + modules: string[] + /** Module IDs that accepted the update (HMR boundaries). */ + boundaries: string[] + /** Propagation graph from changed file to HMR boundaries. */ + graph: { nodes: HmrGraphNode[], edges: HmrGraphEdge[] } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3468c58b5..cd0f098d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -229,6 +229,9 @@ catalogs: specifier: ^2.0.17 version: 2.0.17 frontend: + '@dagrejs/dagre': + specifier: ^3.0.0 + version: 3.0.0 '@floating-ui/dom': specifier: ^1.8.0 version: 1.8.0 @@ -238,6 +241,9 @@ catalogs: '@json-render/vue': specifier: ^0.20.0 version: 0.20.0 + '@vue-flow/core': + specifier: ^1.48.2 + version: 1.48.2 '@vueuse/components': specifier: ^14.4.0 version: 14.4.0 @@ -1217,6 +1223,9 @@ importers: specifier: ^8.2.2 version: 8.2.2(@types/node@25.0.3)(@vitejs/devtools@0.5.2)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.44.1)(tsx@4.23.13)(yaml@2.9.0) devDependencies: + '@dagrejs/dagre': + specifier: catalog:frontend + version: 3.0.0 '@floating-ui/dom': specifier: catalog:frontend version: 1.8.0 @@ -1235,6 +1244,9 @@ importers: '@vitejs/devtools-ui': specifier: workspace:* version: link:../ui + '@vue-flow/core': + specifier: catalog:frontend + version: 1.48.2(vue@3.5.42(typescript@6.0.3)) '@vueuse/components': specifier: catalog:frontend version: 14.4.0(vue@3.5.42(typescript@6.0.3)) @@ -1691,6 +1703,12 @@ packages: '@colordx/core@5.4.3': resolution: {integrity: sha512-kIxYSfA5T8HXjav55UaaH/o/cKivF6jCCGIb8eqtcsfI46wsvlSiT8jMDyrl779qLec3c2c2oHBZo4oAhvbjrQ==} + '@dagrejs/dagre@3.0.0': + resolution: {integrity: sha512-ZzhnTy1rfuoew9Ez3EIw4L2znPGnYYhfn8vc9c4oB8iw6QAsszbiU0vRhlxWPFnmmNSFAkrYeF1PhM5m4lAN0Q==} + + '@dagrejs/graphlib@4.0.1': + resolution: {integrity: sha512-IvcV6FduIIAmLwnH+yun+QtV36SC7mERqa86aClNqmMN09WhmPPYU8ckHrZBozErf+UvHPWOTJYaGYiIcs0DgA==} + '@devframes/hub-ui@0.9.8': resolution: {integrity: sha512-Le2Nbr9FW7SuP/RG9LDWlvLuUooBwt05gJF9l//zA7o8B8vUtNZ9aAxI0JAzs3iXLkp4dPSEmGVdZm38z43L/A==} peerDependencies: @@ -4415,6 +4433,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} @@ -4885,6 +4906,11 @@ packages: typescript: optional: true + '@vue-flow/core@1.48.2': + resolution: {integrity: sha512-raxhgKWE+G/mcEvXJjGFUDYW9rAI3GOtiHR3ZkNpwBWuIaCC1EYiBmKGwJOoNzVFgwO7COgErnK7i08i287AFA==} + peerDependencies: + vue: ^3.3.0 + '@vue-macros/common@3.1.4': resolution: {integrity: sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==} engines: {node: '>=20.19.0'} @@ -4960,6 +4986,9 @@ packages: peerDependencies: vue: ^3.5.0 + '@vueuse/core@10.11.1': + resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==} + '@vueuse/core@14.4.0': resolution: {integrity: sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==} peerDependencies: @@ -5007,6 +5036,9 @@ packages: universal-cookie: optional: true + '@vueuse/metadata@10.11.1': + resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==} + '@vueuse/metadata@14.4.0': resolution: {integrity: sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g==} @@ -5022,6 +5054,9 @@ packages: vue: ^3.5.0 vue-router: ^4.0.0 || ^5.0.0 + '@vueuse/shared@10.11.1': + resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} + '@vueuse/shared@14.4.0': resolution: {integrity: sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==} peerDependencies: @@ -10188,6 +10223,12 @@ snapshots: '@colordx/core@5.4.3': {} + '@dagrejs/dagre@3.0.0': + dependencies: + '@dagrejs/graphlib': 4.0.1 + + '@dagrejs/graphlib@4.0.1': {} + '@devframes/hub-ui@0.9.8(@devframes/hub@0.9.8(crossws@0.4.12(srvx@0.11.22))(devframe@0.9.8(cac@7.0.0)(srvx@0.11.22)))(@devframes/json-render@0.9.8(@devframes/hub@0.8.2(devframe@0.8.2(cac@7.0.0)(srvx@0.11.22)))(devframe@0.8.2(cac@7.0.0)(srvx@0.11.22)))(devframe@0.9.8(cac@7.0.0)(srvx@0.11.22))': dependencies: '@devframes/hub': 0.9.8(crossws@0.4.12(srvx@0.11.22))(devframe@0.9.8(cac@7.0.0)(srvx@0.11.22)) @@ -13453,6 +13494,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/web-bluetooth@0.0.20': {} + '@types/web-bluetooth@0.0.21': {} '@types/webextension-polyfill@0.8.3': {} @@ -14380,6 +14423,17 @@ snapshots: optionalDependencies: typescript: 6.0.3 + '@vue-flow/core@1.48.2(vue@3.5.42(typescript@6.0.3))': + dependencies: + '@vueuse/core': 10.11.1(vue@3.5.42(typescript@6.0.3)) + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + vue: 3.5.42(typescript@6.0.3) + transitivePeerDependencies: + - '@vue/composition-api' + '@vue-macros/common@3.1.4(vue@3.5.42(typescript@6.0.3))': dependencies: '@vue/compiler-sfc': 3.5.42 @@ -14520,6 +14574,16 @@ snapshots: '@vueuse/shared': 14.4.0(vue@3.5.42(typescript@6.0.3)) vue: 3.5.42(typescript@6.0.3) + '@vueuse/core@10.11.1(vue@3.5.42(typescript@6.0.3))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 10.11.1 + '@vueuse/shared': 10.11.1(vue@3.5.42(typescript@6.0.3)) + vue-demi: 0.14.10(vue@3.5.42(typescript@6.0.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + '@vueuse/core@14.4.0(vue@3.5.42(typescript@6.0.3))': dependencies: '@types/web-bluetooth': 0.0.21 @@ -14538,6 +14602,8 @@ snapshots: fuse.js: 7.5.0 idb-keyval: 6.3.0 + '@vueuse/metadata@10.11.1': {} + '@vueuse/metadata@14.4.0': {} '@vueuse/nuxt@14.4.0(magic-string@1.2.3)(magicast@0.5.4)(nuxt@4.5.2(76d30c92021ceef32ae0f10f44f35e04))(oxc-parser@0.147.0)(rolldown@1.2.6)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.60.2)(vite@8.2.2)(webpack@5.104.1(esbuild@0.28.2)))(vue@3.5.42(typescript@6.0.3))': @@ -14576,6 +14642,13 @@ snapshots: vue: 3.5.42(typescript@6.0.3) vue-router: 5.3.0(@vue/compiler-sfc@3.5.42)(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.60.2)(vite@8.2.2)(vue@3.5.42(typescript@6.0.3))(webpack@5.104.1(esbuild@0.28.2)) + '@vueuse/shared@10.11.1(vue@3.5.42(typescript@6.0.3))': + dependencies: + vue-demi: 0.14.10(vue@3.5.42(typescript@6.0.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + '@vueuse/shared@14.4.0(vue@3.5.42(typescript@6.0.3))': dependencies: vue: 3.5.42(typescript@6.0.3) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ea9f47fc7..c3e67d647 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -142,10 +142,12 @@ catalogs: vitepress-plugin-group-icons: ^1.7.6 vitepress-plugin-mermaid: ^2.0.17 frontend: + '@dagrejs/dagre': ^3.0.0 '@floating-ui/dom': ^1.8.0 '@humanwhocodes/momoa': ^3.3.12 '@json-render/core': ^0.20.0 '@json-render/vue': ^0.20.0 + '@vue-flow/core': ^1.48.2 '@vueuse/components': *vueuse '@vueuse/core': *vueuse '@vueuse/router': *vueuse