|
| 1 | +import type { Nitro } from 'nitropack' |
| 2 | +import type { Nuxt } from 'nuxt/schema' |
| 3 | +import type { ResolvedConfig } from 'vite' |
| 4 | +import type { NuxtDevtoolsServerContext, NuxtServerData } from '../types' |
| 5 | +import { createDataInspectorDevframe, registerDataSource } from '@devframes/plugin-data-inspector' |
| 6 | +import { deprecate, NUXT_DEVTOOLS_GROUP_ID, onDevtoolsReady } from '@nuxt/devtools-kit' |
| 7 | +import { mountDevframe } from '@vitejs/devtools-kit/node' |
| 8 | + |
| 9 | +/** |
| 10 | + * Live capture of the Nuxt server-side configuration surfaced by the Data |
| 11 | + * Inspector's `nuxt:application` source (and by the deprecated `getServerData` |
| 12 | + * RPC shim below). |
| 13 | + * |
| 14 | + * This state is module-scoped on purpose: Devframe's data-source registry is |
| 15 | + * process-global and not host-scoped, so this integration supports exactly one |
| 16 | + * active Nuxt DevTools instance per Node process, keyed by the fixed |
| 17 | + * `nuxt:application` source id. Running two live Nuxt DevTools hosts in one |
| 18 | + * process is unsupported. |
| 19 | + */ |
| 20 | +interface CapturedServerData { |
| 21 | + nitro?: Nitro |
| 22 | + viteClient?: ResolvedConfig |
| 23 | + viteSsr?: ResolvedConfig |
| 24 | +} |
| 25 | + |
| 26 | +const captured: CapturedServerData = {} |
| 27 | + |
| 28 | +/** |
| 29 | + * Strip the live Vite instance and other non-serializable branches so the |
| 30 | + * config survives the RPC transport used by the deprecated `getServerData` |
| 31 | + * shim. Preserved verbatim from the removed `server-rpc/server-data.ts` to keep |
| 32 | + * the shim's payload byte-for-byte compatible for its migration window. |
| 33 | + * |
| 34 | + * The live Data Inspector source does **not** use this — it hands the raw |
| 35 | + * objects to the Data Inspector engine, which owns normalization, circular |
| 36 | + * references, and depth limits. |
| 37 | + */ |
| 38 | +function normalizeViteConfig(config: ResolvedConfig): ResolvedConfig { |
| 39 | + return { |
| 40 | + ...config, |
| 41 | + environments: Object.fromEntries( |
| 42 | + Object.entries(config.env ?? {}).map(([key, _]) => { |
| 43 | + return [key, null] |
| 44 | + }), |
| 45 | + ), |
| 46 | + plugins: (config.plugins ?? []).map((i) => { |
| 47 | + const clone = { ...i } |
| 48 | + delete clone.api |
| 49 | + return clone |
| 50 | + }), |
| 51 | + inlineConfig: null, |
| 52 | + } as any as ResolvedConfig |
| 53 | +} |
| 54 | + |
| 55 | +/** |
| 56 | + * Register the live `Nuxt Application` Data Inspector source, capture the raw |
| 57 | + * Nitro/Vite configuration as the Nuxt hooks fire, and mount the Data Inspector |
| 58 | + * Devframe into the Nuxt dock group. |
| 59 | + * |
| 60 | + * Always mounted when Nuxt DevTools is enabled; there is no module option. |
| 61 | + * Ecosystem modules that want more sources install |
| 62 | + * `@devframes/plugin-data-inspector` themselves and use its native |
| 63 | + * `registerDataSource`. |
| 64 | + */ |
| 65 | +export function setup(ctx: NuxtDevtoolsServerContext): void { |
| 66 | + const { nuxt } = ctx |
| 67 | + |
| 68 | + // Capture raw Nitro options once Nitro is created. |
| 69 | + nuxt.hook('nitro:build:before', (nitro) => { |
| 70 | + captured.nitro = nitro |
| 71 | + }) |
| 72 | + |
| 73 | + // Capture the raw resolved Vite config for each environment. Nuxt fires |
| 74 | + // `vite:configResolved` once with `isClient` and once with `isServer`, for |
| 75 | + // both serial Vite servers and Environment API projections, so the source |
| 76 | + // contract stays exactly `vite: { client, ssr }`. Nuxt marks this hook |
| 77 | + // deprecated, but it is the only current host API that reports both configs |
| 78 | + // semantically; the returned-environment-plugin path is unreliable in Vite 8 |
| 79 | + // (Vite resolves those plugins after top-level `configResolved`). |
| 80 | + nuxt.hook('vite:configResolved', (config, env) => { |
| 81 | + if (env.isClient) |
| 82 | + captured.viteClient = config as unknown as ResolvedConfig |
| 83 | + if (env.isServer) |
| 84 | + captured.viteSsr = config as unknown as ResolvedConfig |
| 85 | + }) |
| 86 | + |
| 87 | + // Register the single live source early with a non-static factory, so Nuxt |
| 88 | + // options are immediately queryable and the Nitro/Vite fields populate as the |
| 89 | + // hooks above run. The registry is process-global and shared with the mounted |
| 90 | + // definition, so no context threading is required. |
| 91 | + const unregister = registerDataSource({ |
| 92 | + id: 'nuxt:application', |
| 93 | + title: 'Nuxt Application', |
| 94 | + description: 'Live Nuxt, Nitro, and Vite configuration', |
| 95 | + icon: 'i-ph:database-duotone', |
| 96 | + static: false, |
| 97 | + data: () => ({ |
| 98 | + nuxt: nuxt.options, |
| 99 | + nitro: captured.nitro?.options, |
| 100 | + vite: { |
| 101 | + client: captured.viteClient, |
| 102 | + ssr: captured.viteSsr, |
| 103 | + }, |
| 104 | + }), |
| 105 | + queries: [ |
| 106 | + { title: 'Overview', query: '', excludeFunctions: true }, |
| 107 | + { title: 'Nuxt options', query: 'nuxt', excludeFunctions: true }, |
| 108 | + { title: 'Nitro options', query: 'nitro', excludeFunctions: true }, |
| 109 | + { title: 'Vite configs', query: 'vite', excludeFunctions: true }, |
| 110 | + ], |
| 111 | + }) |
| 112 | + |
| 113 | + // Avoid leaking a process-global source (e.g. across test fixtures). |
| 114 | + nuxt.hook('close', () => { |
| 115 | + unregister() |
| 116 | + captured.nitro = undefined |
| 117 | + captured.viteClient = undefined |
| 118 | + captured.viteSsr = undefined |
| 119 | + }) |
| 120 | + |
| 121 | + // Mount the Data Inspector's bundled SPA as a member of the Nuxt group. The |
| 122 | + // definition defaults to the `~builtin` category, so the category must be |
| 123 | + // overridden per-mount: group members do not inherit their group's category. |
| 124 | + const definition = createDataInspectorDevframe({ exampleSource: false }) |
| 125 | + onDevtoolsReady((kit) => { |
| 126 | + return mountDevframe(kit, definition, { |
| 127 | + dock: { |
| 128 | + groupId: NUXT_DEVTOOLS_GROUP_ID, |
| 129 | + category: 'framework', |
| 130 | + defaultOrder: -100, |
| 131 | + }, |
| 132 | + }) |
| 133 | + }, nuxt) |
| 134 | +} |
| 135 | + |
| 136 | +/** |
| 137 | + * @deprecated The read-only Nuxt Options Viewer has been replaced by the Data |
| 138 | + * Inspector panel's live `Nuxt Application` source. This RPC is kept as a |
| 139 | + * compatibility shim for one migration window and emits `NDT_DEP_0009` on first |
| 140 | + * use; it will be removed in a future major. |
| 141 | + * |
| 142 | + * Unlike the live source, this returns the legacy `NuxtServerData` shape |
| 143 | + * (`vite: { server, client }`) with the Vite configs normalized for RPC |
| 144 | + * transport. |
| 145 | + */ |
| 146 | +export function getServerData(nuxt: Nuxt): NuxtServerData { |
| 147 | + deprecate(nuxt, 'NDT_DEP_0009', { |
| 148 | + api: 'getServerData()', |
| 149 | + replacement: 'the Nuxt Application source in the Data Inspector panel', |
| 150 | + }) |
| 151 | + return { |
| 152 | + nuxt: nuxt.options, |
| 153 | + nitro: captured.nitro?.options, |
| 154 | + vite: { |
| 155 | + server: captured.viteSsr ? normalizeViteConfig(captured.viteSsr) : undefined, |
| 156 | + client: captured.viteClient ? normalizeViteConfig(captured.viteClient) : undefined, |
| 157 | + }, |
| 158 | + // `nuxt.options` is `@nuxt/schema`'s `NuxtOptions` while `NuxtServerData` |
| 159 | + // pins the structurally-identical `nuxt/schema` one; bridge the two. |
| 160 | + } as NuxtServerData |
| 161 | +} |
| 162 | + |
| 163 | +/** |
| 164 | + * @internal |
| 165 | + */ |
| 166 | +export function resetCapturedServerData(): void { |
| 167 | + captured.nitro = undefined |
| 168 | + captured.viteClient = undefined |
| 169 | + captured.viteSsr = undefined |
| 170 | +} |
0 commit comments