From 676ed8b5131c1fabeea1a767c37d6f492d3d8eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Mon, 9 Nov 2026 22:33:35 +0000 Subject: [PATCH 1/6] fix(dev,build): leave Nitro out of the versions when it does not build the server The startup banner and the dev UI's info view listed the installed Nitro version even when another server builder, such as @nuxt/vite-server, was configured. The banner now checks the configured server builder, and the dev server reports the app's builders to the dev UI, which also lets the info view name the configured app builder instead of assuming Vite. Amp-Thread-ID: https://ampcode.com/threads/T-01a1ba1d-392b-7447-9cc3-9587ae9be465 --- packages/nuxt-cli/src/commands/dev.ts | 6 +++++- packages/nuxt-cli/src/dev/index.ts | 14 +++++++++++++- packages/nuxt-cli/src/dev/tui/controller.ts | 5 ++++- packages/nuxt-cli/src/dev/tui/index.ts | 14 +++++++++++--- packages/nuxt-cli/src/dev/utils.ts | 17 ++++++++++++++++- packages/nuxt-cli/src/utils/banner.ts | 13 ++++++++++--- .../test/unit/commands/dev-run.spec.ts | 1 + packages/nuxt-cli/test/unit/dev-tui.spec.ts | 19 +++++++++++++++++++ .../nuxt-cli/test/unit/utils/banner.spec.ts | 10 ++++++++++ 9 files changed, 89 insertions(+), 10 deletions(-) diff --git a/packages/nuxt-cli/src/commands/dev.ts b/packages/nuxt-cli/src/commands/dev.ts index 9d1014c5a..8c1c619c9 100644 --- a/packages/nuxt-cli/src/commands/dev.ts +++ b/packages/nuxt-cli/src/commands/dev.ts @@ -260,7 +260,7 @@ const command = defineCommand({ throw error }) - const { listener, close, reload, onRestart, onReady, onLoading, onEachReady, onLog, onRequests, onSpans, onRoutes, onBuilding, onReport, onReportClear, onFileChange } = started + const { listener, close, reload, onRestart, onReady, onLoading, onEachReady, onLog, onRequests, onSpans, onRoutes, onStack, onBuilding, onReport, onReportClear, onFileChange } = started /** Feed the dev UI from the server running in this process. */ function attachDevUI(devUI: DevUIController): DevUIController { @@ -273,6 +273,7 @@ const command = defineCommand({ onReport(report => devUI.pushReport(report)) onReportClear(id => devUI.clearReport(id)) onRoutes(payload => devUI.setRoutes(payload)) + onStack(payload => devUI.setStack(payload)) return devUI } @@ -387,6 +388,9 @@ const command = defineCommand({ else if (message.type === 'nuxt:internal:dev:routes') { devUI.setRoutes(message.payload) } + else if (message.type === 'nuxt:internal:dev:stack') { + devUI.setStack(message.payload) + } else if (message.type === 'nuxt:internal:dev:building') { devUI.setStatus(message.building ? 'building' : 'ready') } diff --git a/packages/nuxt-cli/src/dev/index.ts b/packages/nuxt-cli/src/dev/index.ts index 677a2558e..4533da6b6 100644 --- a/packages/nuxt-cli/src/dev/index.ts +++ b/packages/nuxt-cli/src/dev/index.ts @@ -6,7 +6,7 @@ import type { DevRestartReason } from './reason' import type { DevReportSummary } from './error-channel' import type { ServerLogEvent } from './log-channel' import type { DevRequestSpan } from './span-channel' -import type { DevRequestEvent, DevRoutes, NuxtDevContext, NuxtDevIPCMessage, NuxtParentIPCMessage } from './utils' +import type { DevRequestEvent, DevRoutes, DevStack, NuxtDevContext, NuxtDevIPCMessage, NuxtParentIPCMessage } from './utils' import process from 'node:process' @@ -242,6 +242,8 @@ interface InitializeReturn { onBuilding: (callback: (building: boolean) => void) => void /** Called whenever the app's routes are (re)discovered. */ onRoutes: (callback: (routes: DevRoutes) => void) => void + /** Called with the builders the app uses, each time Nuxt loads. */ + onStack: (callback: (stack: DevStack) => void) => void /** Called the first time a watched file changes, before Nuxt reloads. */ onFileChange: (callback: () => void) => void onRestart: (callback: (reason?: DevRestartReason) => void) => void @@ -318,6 +320,7 @@ export async function initialize(devContext: NuxtDevContext, ctx: InitializeOpti const requests = createFeed(PENDING_REQUEST_BATCHES) const spans = createFeed(PENDING_REQUEST_BATCHES) const routes = createFeed(1) + const stack = createFeed(1) const building = createFeed(0) const reports = createFeed(PENDING_REPORTS) const reportsCleared = createFeed(0) @@ -388,6 +391,14 @@ export async function initialize(devContext: NuxtDevContext, ctx: InitializeOpti building.emit(value) }) + devServer.on('stack', (payload) => { + if (ipc.enabled) { + ipc.send({ type: 'nuxt:internal:dev:stack', payload }) + return + } + stack.emit(payload) + }) + devServer.on('routes', (payload) => { if (ipc.enabled) { ipc.send({ type: 'nuxt:internal:dev:routes', payload }) @@ -549,6 +560,7 @@ export async function initialize(devContext: NuxtDevContext, ctx: InitializeOpti onReport: reports.subscribe, onReportClear: reportsCleared.subscribe, onRoutes: routes.subscribe, + onStack: stack.subscribe, onFileChange: (callback: () => void) => { devServer.once('change', callback) }, diff --git a/packages/nuxt-cli/src/dev/tui/controller.ts b/packages/nuxt-cli/src/dev/tui/controller.ts index 4239789f2..41a35d329 100644 --- a/packages/nuxt-cli/src/dev/tui/controller.ts +++ b/packages/nuxt-cli/src/dev/tui/controller.ts @@ -3,7 +3,7 @@ import type { DevReportSummary } from '../error-channel' import type { ServerLogEvent } from '../log-channel' import type { ShortcutContext } from '../shortcuts' import type { DevRequestSpan } from '../span-channel' -import type { DevRequestEvent, DevRoutes } from '../utils' +import type { DevRequestEvent, DevRoutes, DevStack } from '../utils' import type { DevUIOptions } from './index' import type { DevStatus } from './panel' import type { DevUISession } from './session' @@ -33,6 +33,8 @@ export interface DevUIController { clearReport: (id?: string) => void /** Replace the routes shown in the route view. */ setRoutes: (routes: DevRoutes) => void + /** Name the builders the loaded app uses, for the versions in the info view. */ + setStack: (stack: DevStack) => void /** * Report the render the server is busy with, or that it is busy with none. * `awaiting` says no page has been rendered yet, which is what makes it a @@ -52,6 +54,7 @@ export const NOOP_CONTROLLER: DevUIController = { pushReport: () => {}, clearReport: () => {}, setRoutes: () => {}, + setStack: () => {}, setRendering: () => {}, } diff --git a/packages/nuxt-cli/src/dev/tui/index.ts b/packages/nuxt-cli/src/dev/tui/index.ts index 5d6a6111d..385a35c15 100644 --- a/packages/nuxt-cli/src/dev/tui/index.ts +++ b/packages/nuxt-cli/src/dev/tui/index.ts @@ -1,5 +1,7 @@ +import type { NuxtOptions } from '@nuxt/schema' import type { TerminalNotification } from '../../utils/terminal-host' import type { ShortcutContext } from '../shortcuts' +import type { DevStack } from '../utils' import type { DevUIController } from './controller' import type { PanelStart } from './first-frame' import type { InfoSection } from './info-overlay' @@ -122,12 +124,13 @@ export function setupDevUI(context: ShortcutContext, options: DevUIOptions = {}) events, }) let shortcuts: UIShortcut[] = [] + let stack: DevStack | undefined let qrCode: string | undefined let armedOpen = false let ready = false const helpOverlay = new HelpOverlay(() => shortcuts.filter(shortcut => shortcut.isAvailable?.() !== false), write, release) const infoOverlay = new InfoOverlay( - () => describeSession(context, cwd, requests, sessionStart, state.update, state.updateLink), + () => describeSession(context, cwd, requests, sessionStart, stack, state.update, state.updateLink), write, release, () => qrCode, @@ -647,6 +650,9 @@ export function setupDevUI(context: ShortcutContext, options: DevUIOptions = {}) update({ note: undefined }) }, setRoutes: payload => routeOverlay.setRoutes(payload), + setStack: (payload) => { + stack = payload + }, setRendering: (pending, awaiting) => { update({ rendering: pending && { label: pending.label, startedAt: pending.startedAt }, @@ -716,10 +722,12 @@ function describeSession( cwd: string, requests: RequestLog, sessionStart: number, + stack?: DevStack, update?: string, updateLink?: string, ): InfoSection[] { - const versions = resolveStackVersions(cwd) + // Until Nuxt has loaded, the installed packages are all there is to go on. + const versions = resolveStackVersions(cwd, { builder: stack?.builder as NuxtOptions['builder'] | undefined }) const { listener } = context return [ @@ -729,7 +737,7 @@ function describeSession( ['Nuxt', update ? `${linkVersion(versions.nuxt)} ${paint('warning', updateLink ?? `\u2192 ${update} available`)}` : linkVersion(versions.nuxt)], - ['Nitro', versions.nitro], + ['Nitro', stack?.nitro === false ? undefined : versions.nitro], [versions.builder.name, versions.builder.version], [versions.builder.provider?.name ?? 'via', versions.builder.provider?.version], ['Vue', versions.vue ?? undefined], diff --git a/packages/nuxt-cli/src/dev/utils.ts b/packages/nuxt-cli/src/dev/utils.ts index f864c3523..c5a6d0be3 100644 --- a/packages/nuxt-cli/src/dev/utils.ts +++ b/packages/nuxt-cli/src/dev/utils.ts @@ -36,7 +36,7 @@ import { loadKit } from '../utils/kit' import { acquireLock, formatLockError, getTakeoverPid, updateLock } from '../utils/lockfile' import { debug, logger, writeNotice } from '../utils/logger' import { loadNuxtManifest, resolveNuxtManifest, writeNuxtManifest } from '../utils/nuxt' -import { resolveServerBuild } from '../utils/server-build' +import { getServerBuilderName, resolveServerBuild } from '../utils/server-build' import { createConfigWatcher, FileChangeTracker, getLocalLayerDirs } from './config-watcher' import { createCliReport, DEFAULT_ERROR_CHANNEL, ERROR_CHANNEL_ENV, handleErrorChannelRequest, isErrorChannelRequest, isLocalPeer, isThreadRunner, openErrorBridge, publishCliProgress, renderErrorPage, resolveChannelPath, summariseReport, useErrorChannel, withErrorChannel } from './error-channel' import { sendErrorResponse } from './error-response' @@ -68,6 +68,7 @@ export type NuxtDevIPCMessage | { type: 'nuxt:internal:dev:requests', requests: DevRequestEvent[] } | { type: 'nuxt:internal:dev:spans', spans: DevRequestSpan[] } | { type: 'nuxt:internal:dev:routes', payload: DevRoutes } + | { type: 'nuxt:internal:dev:stack', payload: DevStack } | { type: 'nuxt:internal:dev:building', building: boolean } | { type: 'nuxt:internal:dev:rendering', pending?: PendingRender, awaiting?: boolean } | { type: 'nuxt:internal:dev:report', report: DevReportSummary } @@ -181,6 +182,14 @@ export interface DevRoutes { errorComponent?: string } +/** What the loaded app is built with, for the versions the dev UI lists. */ +export interface DevStack { + /** The configured app builder, e.g. `vite` or `@nuxt/webpack-builder`. */ + builder?: string + /** Whether Nitro builds the server, rather than another server builder. */ + nitro: boolean +} + /** A page or server route the app defines, as listed in the dev UI. */ export interface DevRoute { kind: 'page' | 'server' @@ -216,6 +225,7 @@ interface DevServerEventMap { /** Time spent serving a request that the app did not publish itself. */ 'span': [span: DevRequestSpan] 'routes': [payload: DevRoutes] + 'stack': [payload: DevStack] 'building': [building: boolean] /** A report the app forwarded, rendered for a terminal. */ 'report': [report: DevReportSummary] @@ -858,6 +868,11 @@ export class NuxtDevServer extends EventEmitter { this.#currentNuxt = await kit.loadNuxt(loadOptions) if (this.options.captureUIEvents) { + const builder = this.#currentNuxt.options.builder + this.emit('stack', { + builder: typeof builder === 'string' ? builder : undefined, + nitro: getServerBuilderName(this.#currentNuxt) === 'Nitro', + }) this.#collectRoutes(this.#currentNuxt) this.#watchServerBuilds(this.#currentNuxt) } diff --git a/packages/nuxt-cli/src/utils/banner.ts b/packages/nuxt-cli/src/utils/banner.ts index 59d17c21e..8613dfc40 100644 --- a/packages/nuxt-cli/src/utils/banner.ts +++ b/packages/nuxt-cli/src/utils/banner.ts @@ -5,6 +5,7 @@ import { styleText } from 'node:util' import { logger } from './logger' import { getNitroVersion } from './nitro' import { getNuxtPkgVersion, getPkgJSON, getPkgVersion } from './pkg' +import { getServerBuilderName } from './server-build' export function getBuilder(cwd: string, builder: Exclude): { name: string, version: string, provider?: { name: string, version: string } } { switch (builder) { @@ -30,7 +31,12 @@ export function getBuilder(cwd: string, builder: Exclude { onRequests: vi.fn(), onSpans: vi.fn(), onRoutes: vi.fn(), + onStack: vi.fn(), onBuilding: vi.fn(), onReport: vi.fn(), onReportClear: vi.fn(), diff --git a/packages/nuxt-cli/test/unit/dev-tui.spec.ts b/packages/nuxt-cli/test/unit/dev-tui.spec.ts index ce26260f2..8398bd361 100644 --- a/packages/nuxt-cli/test/unit/dev-tui.spec.ts +++ b/packages/nuxt-cli/test/unit/dev-tui.spec.ts @@ -36,6 +36,9 @@ vi.mock('../../src/dev/listen', async importOriginal => ({ openBrowser: (url: string) => void opened.push(url), })) +// The info view resolves versions from what is installed; pinned so it lists Nitro. +vi.mock('../../src/utils/nitro', () => ({ getNitroVersion: () => '2.13.5' })) + const copied: string[] = [] vi.mock('tinyclip', () => ({ writeText: (text: string) => { @@ -3013,6 +3016,22 @@ async function withPanel(run: (ui: ReturnType, settle: () => } } +describe('panel info view', () => { + it('lists Nitro until the app says another builder runs the server', async () => { + await withPanel(async (_ui, settle) => { + process.stdin.emit('keypress', 'i', { name: 'i', sequence: 'i' }) + expect(await settle()).toMatch(/Nitro\s+2\.13\.5/) + }) + await withPanel(async (ui, settle) => { + ui.setStack({ builder: 'vite', nitro: false }) + process.stdin.emit('keypress', 'i', { name: 'i', sequence: 'i' }) + const frame = await settle() + expect(frame).toContain('versions') + expect(frame).not.toContain('Nitro') + }) + }) +}) + describe('panel shortcut routing', () => { it.each(['ready', 'starting', 'building', 'restarting', 'warming', 'error'] as const)('quits without confirmation while %s', async (status) => { const interrupt = vi.fn() diff --git a/packages/nuxt-cli/test/unit/utils/banner.spec.ts b/packages/nuxt-cli/test/unit/utils/banner.spec.ts index 9e6a8e84a..08d70b03a 100644 --- a/packages/nuxt-cli/test/unit/utils/banner.spec.ts +++ b/packages/nuxt-cli/test/unit/utils/banner.spec.ts @@ -142,6 +142,16 @@ describe('showBanner', () => { `) }) + it('should omit nitro when another server builder is configured', async () => { + const renderer = await render(() => + showBanner({ _version: '4.4.6', options: { rootDir: '/any', builder: 'vite', server: { builder: '@nuxt/vite-server' } } } as unknown as Nuxt)) + + expect(screen(renderer)).toMatchInlineSnapshot(` + "│ + ● Nuxt 4.4.6 (with Vite 7.3.1 and Vue 3.5.39)" + `) + }) + it('should identify Vite+ as the Vite provider', async () => { const renderer = await render(() => showBanner({ _version: '4.4.6', options: { rootDir: '/vite-plus', builder: 'vite' } } as unknown as Nuxt)) From 61ffeccf4555a6f276a550910d4bb7256021ffcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Mon, 9 Nov 2026 23:01:28 +0000 Subject: [PATCH 2/6] feat(info): report the server builder instead of the Nitro version nuxt info printed the installed Nitro version even for apps built with another server builder. It now prints a Server builder row in the same name@version form as Builder: nitro@2.13.6 by default, or the package server.builder names, such as @nuxt/vite-server@4.6.4. In --json the nitroVersion key becomes serverBuilder. Amp-Thread-ID: https://ampcode.com/threads/T-01a1ba1d-392b-7447-9cc3-9587ae9be465 --- packages/nuxt-cli/src/commands/info.ts | 31 +++++++++++++++++-- .../test/unit/commands/info-run.spec.ts | 16 ++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/nuxt-cli/src/commands/info.ts b/packages/nuxt-cli/src/commands/info.ts index 640af78a3..80ae17fb8 100644 --- a/packages/nuxt-cli/src/commands/info.ts +++ b/packages/nuxt-cli/src/commands/info.ts @@ -1,4 +1,4 @@ -import type { NuxtModule } from '@nuxt/schema' +import type { Nuxt, NuxtConfig, NuxtModule } from '@nuxt/schema' import type { PackageJson } from 'pkg-types' import os from 'node:os' @@ -23,6 +23,7 @@ import { readDependencyPackageJson } from '../utils/package-json' import { detectPackageManager, getPackageManagerVersion } from '../utils/package-managers' import { resolveRootDir } from '../utils/paths' import { tryResolveNuxt } from '../utils/resolve-nuxt' +import { getServerBuilderName } from '../utils/server-build' import { rootDirArgs } from './_shared' const LEADING_SLASH_RE = /^\// @@ -37,7 +38,7 @@ const JSON_KEYS: Record = { 'nuxt/cli version': 'cliVersion', 'Package manager': 'packageManager', 'Nuxt version': 'nuxtVersion', - 'Nitro version': 'nitroVersion', + 'Server builder': 'serverBuilder', 'Builder': 'builder', 'Config': 'config', 'Modules': 'modules', @@ -94,6 +95,7 @@ export default defineCommand({ const configKeys = Object.keys(nuxtConfig).sort() const moduleNames = modules.filter(module => module !== null) const builder = nuxtConfig.builder || 'vite' + const serverBuilder = await describeServerBuilder(nuxtConfig, nitroVersion, getDepVersion) const osType = os.type() const cpus = os.cpus() const builderInfo = typeof builder === 'string' && ['vite', '@nuxt/vite-builder', 'webpack', '@nuxt/webpack-builder', 'rspack', '@nuxt/rspack-builder'].includes(builder) @@ -113,7 +115,7 @@ export default defineCommand({ 'nuxt/cli version': nuxiVersion, 'Package manager': packageManager, 'Nuxt version': nuxtVersion, - 'Nitro version': nitroVersion, + 'Server builder': serverBuilder, 'Builder': builderInfo.name === 'custom' ? 'custom' : `${builderInfo.name.toLowerCase()}@${builderInfo.version}`, 'Config': configKeys.map(key => `\`${key}\``).join(', '), 'Modules': moduleNames.map(name => `\`${name}\``).join(', '), @@ -253,3 +255,26 @@ export function normalizeConfigModule( } return null } + +/** + * The server builder as `name@version`, like the app builder above it: Nitro by + * default, otherwise the package `server.builder` names, or `custom` for one + * defined inline. + */ +async function describeServerBuilder( + nuxtConfig: NuxtConfig, + nitroVersion: string | undefined, + getDepVersion: (name: string) => Promise, +): Promise { + // Read from the config, without loading Nuxt, the same way as for a loaded app. + const name = getServerBuilderName({ options: nuxtConfig } as unknown as Nuxt) + if (name === 'Nitro') { + return nitroVersion ? `nitro@${nitroVersion}` : 'nitro' + } + const specifier = (nuxtConfig as { server?: { builder?: unknown } }).server?.builder + if (typeof specifier !== 'string') { + return name + } + const version = await getDepVersion(specifier) + return version ? `${specifier}@${version}` : specifier +} diff --git a/packages/nuxt-cli/test/unit/commands/info-run.spec.ts b/packages/nuxt-cli/test/unit/commands/info-run.spec.ts index d2ef904b8..67925c8c0 100644 --- a/packages/nuxt-cli/test/unit/commands/info-run.spec.ts +++ b/packages/nuxt-cli/test/unit/commands/info-run.spec.ts @@ -96,6 +96,22 @@ describe('info command', () => { } }) + it('should name Nitro as the default server builder', async () => { + await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true })) + + expect((await runInfoJSON()).serverBuilder).toMatch(/^nitro@\d/) + }) + + it('should name the server builder a config chooses, with its version', async () => { + await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true, dependencies: { '@nuxt/vite-server': '4.6.4' } })) + await writeFile(join(cwd, 'nuxt.config.mjs'), `export default { server: { builder: '@nuxt/vite-server' } }`) + + const payload = await runInfoJSON() + + expect(payload.serverBuilder).toMatch(/^@nuxt\/vite-server@\d/) + expect(payload).not.toHaveProperty('nitroVersion') + }) + it('should still report on a project with no config', async () => { await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true })) From e1037283847573ff88fc9f1cc3620e0997936d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Tue, 10 Nov 2026 09:59:47 +0000 Subject: [PATCH 3/6] fix(dev,info): read server builder shorthands, and report the stack after building nuxt info now reads the 'vite' and 'nitro' shorthands Nuxt accepts for server.builder as the packages they stand for, so it no longer reports the vite package's version for @nuxt/vite-server. The dev server also reports the stack again once Nuxt has built, because a server builder defined inline only describes itself while it builds. Amp-Thread-ID: https://ampcode.com/threads/T-01a1ba1d-392b-7447-9cc3-9587ae9be465 --- packages/nuxt-cli/src/commands/info.ts | 11 ++++++++-- packages/nuxt-cli/src/dev/utils.ts | 20 ++++++++++++++----- .../test/unit/commands/info-run.spec.ts | 7 +++++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/packages/nuxt-cli/src/commands/info.ts b/packages/nuxt-cli/src/commands/info.ts index 80ae17fb8..0203bb2af 100644 --- a/packages/nuxt-cli/src/commands/info.ts +++ b/packages/nuxt-cli/src/commands/info.ts @@ -28,6 +28,12 @@ import { rootDirArgs } from './_shared' const LEADING_SLASH_RE = /^\// +/** The shorthands Nuxt accepts for `server.builder`, and the packages they stand for. */ +const SERVER_BUILDER_SHORTHANDS: Record = { + nitro: '@nuxt/nitro-server', + vite: '@nuxt/vite-server', +} + /** JSON key for each display label, so `--json` output stays stable if a label is reworded. */ const JSON_KEYS: Record = { 'Operating system': 'operatingSystem', @@ -271,10 +277,11 @@ async function describeServerBuilder( if (name === 'Nitro') { return nitroVersion ? `nitro@${nitroVersion}` : 'nitro' } - const specifier = (nuxtConfig as { server?: { builder?: unknown } }).server?.builder - if (typeof specifier !== 'string') { + const configured = (nuxtConfig as { server?: { builder?: unknown } }).server?.builder + if (typeof configured !== 'string') { return name } + const specifier = SERVER_BUILDER_SHORTHANDS[configured] ?? configured const version = await getDepVersion(specifier) return version ? `${specifier}@${version}` : specifier } diff --git a/packages/nuxt-cli/src/dev/utils.ts b/packages/nuxt-cli/src/dev/utils.ts index c5a6d0be3..2e1c73918 100644 --- a/packages/nuxt-cli/src/dev/utils.ts +++ b/packages/nuxt-cli/src/dev/utils.ts @@ -868,11 +868,7 @@ export class NuxtDevServer extends EventEmitter { this.#currentNuxt = await kit.loadNuxt(loadOptions) if (this.options.captureUIEvents) { - const builder = this.#currentNuxt.options.builder - this.emit('stack', { - builder: typeof builder === 'string' ? builder : undefined, - nitro: getServerBuilderName(this.#currentNuxt) === 'Nitro', - }) + this.#emitStack(this.#currentNuxt) this.#collectRoutes(this.#currentNuxt) this.#watchServerBuilds(this.#currentNuxt) } @@ -900,6 +896,15 @@ export class NuxtDevServer extends EventEmitter { }) } + /** Report which builders the app uses, for the versions the dev UI lists. */ + #emitStack(nuxt: Nuxt): void { + const builder = nuxt.options.builder + this.emit('stack', { + builder: typeof builder === 'string' ? builder : undefined, + nitro: getServerBuilderName(nuxt) === 'Nitro', + }) + } + /** * Gather the app's routes. * @@ -1232,6 +1237,11 @@ export class NuxtDevServer extends EventEmitter { await Promise.all([typesPromise, kit.buildNuxt(this.#currentNuxt)]) + // A server builder defined inline describes itself only once it has built. + if (this.options.captureUIEvents) { + this.#emitStack(this.#currentNuxt) + } + if (!this.#currentNuxt.server) { throw new ActionableError(noDevServerMessage(serverBuild.label, true)) } diff --git a/packages/nuxt-cli/test/unit/commands/info-run.spec.ts b/packages/nuxt-cli/test/unit/commands/info-run.spec.ts index 67925c8c0..a42f98428 100644 --- a/packages/nuxt-cli/test/unit/commands/info-run.spec.ts +++ b/packages/nuxt-cli/test/unit/commands/info-run.spec.ts @@ -112,6 +112,13 @@ describe('info command', () => { expect(payload).not.toHaveProperty('nitroVersion') }) + it('should read the `vite` shorthand as the package it stands for', async () => { + await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true })) + await writeFile(join(cwd, 'nuxt.config.mjs'), `export default { server: { builder: 'vite' } }`) + + expect((await runInfoJSON()).serverBuilder).toMatch(/^@nuxt\/vite-server@\d/) + }) + it('should still report on a project with no config', async () => { await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true })) From 435df559e13fa3e7000e11a4f407052214d1d812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Tue, 10 Nov 2026 10:07:49 +0000 Subject: [PATCH 4/6] test(info): declare builder versions in the server builder fixtures Amp-Thread-ID: https://ampcode.com/threads/T-01a1ba1d-392b-7447-9cc3-9587ae9be465 --- packages/nuxt-cli/test/unit/commands/info-run.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/nuxt-cli/test/unit/commands/info-run.spec.ts b/packages/nuxt-cli/test/unit/commands/info-run.spec.ts index a42f98428..c311b6418 100644 --- a/packages/nuxt-cli/test/unit/commands/info-run.spec.ts +++ b/packages/nuxt-cli/test/unit/commands/info-run.spec.ts @@ -96,8 +96,9 @@ describe('info command', () => { } }) + // Versions are declared so the fixtures resolve one even where nothing is installed. it('should name Nitro as the default server builder', async () => { - await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true })) + await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true, dependencies: { nitropack: '2.13.6' } })) expect((await runInfoJSON()).serverBuilder).toMatch(/^nitro@\d/) }) @@ -113,7 +114,7 @@ describe('info command', () => { }) it('should read the `vite` shorthand as the package it stands for', async () => { - await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true })) + await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true, dependencies: { '@nuxt/vite-server': '4.6.4' } })) await writeFile(join(cwd, 'nuxt.config.mjs'), `export default { server: { builder: 'vite' } }`) expect((await runInfoJSON()).serverBuilder).toMatch(/^@nuxt\/vite-server@\d/) From 6dc09420da720a692ef1a44c3237e7ba26d2ef9b Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 10 Nov 2026 15:02:01 +0000 Subject: [PATCH 5/6] fix(dev,info): name non-Nitro server builders and keep nitroVersion in json --- packages/nuxt-cli/src/commands/info.ts | 39 ++++++++++--------- packages/nuxt-cli/src/dev/tui/index.ts | 12 +++++- packages/nuxt-cli/src/dev/utils.ts | 8 ++-- packages/nuxt-cli/src/utils/server-build.ts | 16 ++++++-- .../test/unit/commands/info-run.spec.ts | 29 +++++++++----- packages/nuxt-cli/test/unit/dev-tui.spec.ts | 8 ++-- .../nuxt-cli/test/unit/dev/initialize.spec.ts | 15 +++++++ .../nuxt-cli/test/unit/dev/lifecycle.spec.ts | 20 ++++++++++ 8 files changed, 107 insertions(+), 40 deletions(-) diff --git a/packages/nuxt-cli/src/commands/info.ts b/packages/nuxt-cli/src/commands/info.ts index 0203bb2af..646c037b6 100644 --- a/packages/nuxt-cli/src/commands/info.ts +++ b/packages/nuxt-cli/src/commands/info.ts @@ -28,10 +28,10 @@ import { rootDirArgs } from './_shared' const LEADING_SLASH_RE = /^\// -/** The shorthands Nuxt accepts for `server.builder`, and the packages they stand for. */ -const SERVER_BUILDER_SHORTHANDS: Record = { - nitro: '@nuxt/nitro-server', - vite: '@nuxt/vite-server', +/** Server builders other than Nitro released with Nuxt, by each `server.builder` value naming them. */ +const NUXT_SERVER_BUILDERS: Record = { + 'vite': 'vite', + '@nuxt/vite-server': 'vite', } /** JSON key for each display label, so `--json` output stays stable if a label is reworded. */ @@ -101,7 +101,11 @@ export default defineCommand({ const configKeys = Object.keys(nuxtConfig).sort() const moduleNames = modules.filter(module => module !== null) const builder = nuxtConfig.builder || 'vite' - const serverBuilder = await describeServerBuilder(nuxtConfig, nitroVersion, getDepVersion) + // Read from the config, without loading Nuxt, the same way as for a loaded app. + const usesNitro = getServerBuilderName({ options: nuxtConfig } as unknown as Nuxt) === 'Nitro' + const serverBuilder = usesNitro + ? nitroVersion ? `nitro@${nitroVersion}` : 'nitro' + : await describeServerBuilder(nuxtConfig, getDepVersion) const osType = os.type() const cpus = os.cpus() const builderInfo = typeof builder === 'string' && ['vite', '@nuxt/vite-builder', 'webpack', '@nuxt/webpack-builder', 'rspack', '@nuxt/rspack-builder'].includes(builder) @@ -137,6 +141,7 @@ export default defineCommand({ const key = JSON_KEYS[label]! return [key, lists[key] ?? (value?.replaceAll('`', '') || null)] })), + nitroVersion: (usesNitro && nitroVersion) || null, }) return } @@ -263,25 +268,23 @@ export function normalizeConfigModule( } /** - * The server builder as `name@version`, like the app builder above it: Nitro by - * default, otherwise the package `server.builder` names, or `custom` for one - * defined inline. + * A server builder other than Nitro: its name when it is released with Nuxt, + * otherwise the package or path `server.builder` names (with the package's + * version), or `custom` for one defined inline. */ async function describeServerBuilder( nuxtConfig: NuxtConfig, - nitroVersion: string | undefined, getDepVersion: (name: string) => Promise, ): Promise { - // Read from the config, without loading Nuxt, the same way as for a loaded app. - const name = getServerBuilderName({ options: nuxtConfig } as unknown as Nuxt) - if (name === 'Nitro') { - return nitroVersion ? `nitro@${nitroVersion}` : 'nitro' - } const configured = (nuxtConfig as { server?: { builder?: unknown } }).server?.builder if (typeof configured !== 'string') { - return name + return 'custom' + } + const nuxtBuilder = NUXT_SERVER_BUILDERS[configured] + if (nuxtBuilder) { + return nuxtBuilder } - const specifier = SERVER_BUILDER_SHORTHANDS[configured] ?? configured - const version = await getDepVersion(specifier) - return version ? `${specifier}@${version}` : specifier + const packageName = getPackageName(configured) + const version = packageName && await getDepVersion(packageName) + return version ? `${configured}@${version}` : configured } diff --git a/packages/nuxt-cli/src/dev/tui/index.ts b/packages/nuxt-cli/src/dev/tui/index.ts index 385a35c15..2f999c38b 100644 --- a/packages/nuxt-cli/src/dev/tui/index.ts +++ b/packages/nuxt-cli/src/dev/tui/index.ts @@ -55,6 +55,12 @@ const NOTICE_MS = 4000 const LOADING_STATUSES = new Set(['starting', 'building', 'restarting']) +/** Display names for the server builders released with Nuxt. */ +const SERVER_BUILDER_NAMES: Record = { + nitro: 'Nitro', + vite: 'Vite', +} + interface UIShortcut { keys: string[] /** Short label for the hint line. Omitted shortcuts live only in the help view. */ @@ -729,6 +735,10 @@ function describeSession( // Until Nuxt has loaded, the installed packages are all there is to go on. const versions = resolveStackVersions(cwd, { builder: stack?.builder as NuxtOptions['builder'] | undefined }) const { listener } = context + const server = stack?.server ?? 'nitro' + const serverName = SERVER_BUILDER_NAMES[server] ?? server + // The Vite server builder shares the app builder's version, listed below. + const serverVersion = server === 'nitro' ? versions.nitro : undefined return [ { @@ -737,7 +747,7 @@ function describeSession( ['Nuxt', update ? `${linkVersion(versions.nuxt)} ${paint('warning', updateLink ?? `\u2192 ${update} available`)}` : linkVersion(versions.nuxt)], - ['Nitro', stack?.nitro === false ? undefined : versions.nitro], + ['Server builder', serverVersion ? `${serverName} ${serverVersion}` : serverName], [versions.builder.name, versions.builder.version], [versions.builder.provider?.name ?? 'via', versions.builder.provider?.version], ['Vue', versions.vue ?? undefined], diff --git a/packages/nuxt-cli/src/dev/utils.ts b/packages/nuxt-cli/src/dev/utils.ts index 2e1c73918..3c1c1c68d 100644 --- a/packages/nuxt-cli/src/dev/utils.ts +++ b/packages/nuxt-cli/src/dev/utils.ts @@ -36,7 +36,7 @@ import { loadKit } from '../utils/kit' import { acquireLock, formatLockError, getTakeoverPid, updateLock } from '../utils/lockfile' import { debug, logger, writeNotice } from '../utils/logger' import { loadNuxtManifest, resolveNuxtManifest, writeNuxtManifest } from '../utils/nuxt' -import { getServerBuilderName, resolveServerBuild } from '../utils/server-build' +import { getServerBuilderId, resolveServerBuild } from '../utils/server-build' import { createConfigWatcher, FileChangeTracker, getLocalLayerDirs } from './config-watcher' import { createCliReport, DEFAULT_ERROR_CHANNEL, ERROR_CHANNEL_ENV, handleErrorChannelRequest, isErrorChannelRequest, isLocalPeer, isThreadRunner, openErrorBridge, publishCliProgress, renderErrorPage, resolveChannelPath, summariseReport, useErrorChannel, withErrorChannel } from './error-channel' import { sendErrorResponse } from './error-response' @@ -186,8 +186,8 @@ export interface DevRoutes { export interface DevStack { /** The configured app builder, e.g. `vite` or `@nuxt/webpack-builder`. */ builder?: string - /** Whether Nitro builds the server, rather than another server builder. */ - nitro: boolean + /** The server builder's short name, e.g. `nitro` or `vite`, when Nuxt has one. */ + server?: string } /** A page or server route the app defines, as listed in the dev UI. */ @@ -901,7 +901,7 @@ export class NuxtDevServer extends EventEmitter { const builder = nuxt.options.builder this.emit('stack', { builder: typeof builder === 'string' ? builder : undefined, - nitro: getServerBuilderName(nuxt) === 'Nitro', + server: getServerBuilderId(nuxt), }) } diff --git a/packages/nuxt-cli/src/utils/server-build.ts b/packages/nuxt-cli/src/utils/server-build.ts index 52cffbd8b..ff541573c 100644 --- a/packages/nuxt-cli/src/utils/server-build.ts +++ b/packages/nuxt-cli/src/utils/server-build.ts @@ -97,10 +97,7 @@ export function tryUseNitro(kit: MaybeModernKit): NitroLike | undefined { * reads as `vite`. */ export function getServerBuilderName(nuxt: Nuxt, hasServer?: boolean): string { - const declared = (nuxt as MaybeModernNuxt).serverBuild - // A descriptor Nuxt seeded but no builder replaced carries the raw specifier - // as its name, so it is read the same way as the `server.builder` option. - const name = declared ? declared.label || normalizeBuilderName(declared.name) : inferBuilderName(nuxt) + const name = (nuxt as MaybeModernNuxt).serverBuild?.label || getServerBuilderId(nuxt) if (name === 'nitro') { return 'Nitro' } @@ -111,6 +108,17 @@ export function getServerBuilderName(nuxt: Nuxt, hasServer?: boolean): string { return hasServer === false ? 'unknown' : 'Nitro' } +/** + * The configured server builder's short name, such as `nitro`, `vite` or + * `custom`, or `undefined` for Nuxt versions without `server.builder`. + */ +export function getServerBuilderId(nuxt: Nuxt): string | undefined { + const declared = (nuxt as MaybeModernNuxt).serverBuild + // A descriptor Nuxt seeded but no builder replaced carries the raw specifier + // as its name, so it is read the same way as the `server.builder` option. + return declared ? normalizeBuilderName(declared.name) : inferBuilderName(nuxt) +} + /** * The server builder named by the `server.builder` option, for Nuxt versions * that do not describe the build themselves. diff --git a/packages/nuxt-cli/test/unit/commands/info-run.spec.ts b/packages/nuxt-cli/test/unit/commands/info-run.spec.ts index c311b6418..f3bb13df8 100644 --- a/packages/nuxt-cli/test/unit/commands/info-run.spec.ts +++ b/packages/nuxt-cli/test/unit/commands/info-run.spec.ts @@ -100,24 +100,35 @@ describe('info command', () => { it('should name Nitro as the default server builder', async () => { await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true, dependencies: { nitropack: '2.13.6' } })) - expect((await runInfoJSON()).serverBuilder).toMatch(/^nitro@\d/) + const payload = await runInfoJSON() + + expect(payload.serverBuilder).toMatch(/^nitro@\d/) + expect(payload.nitroVersion).toMatch(/^\d/) }) - it('should name the server builder a config chooses, with its version', async () => { + it.each(['@nuxt/vite-server', 'vite'])('should name the Vite server builder from `%s` without a version', async (builder) => { await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true, dependencies: { '@nuxt/vite-server': '4.6.4' } })) - await writeFile(join(cwd, 'nuxt.config.mjs'), `export default { server: { builder: '@nuxt/vite-server' } }`) + await writeFile(join(cwd, 'nuxt.config.mjs'), `export default { server: { builder: '${builder}' } }`) const payload = await runInfoJSON() - expect(payload.serverBuilder).toMatch(/^@nuxt\/vite-server@\d/) - expect(payload).not.toHaveProperty('nitroVersion') + expect(payload.serverBuilder).toBe('vite') + expect(payload.nitroVersion).toBeNull() }) - it('should read the `vite` shorthand as the package it stands for', async () => { - await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true, dependencies: { '@nuxt/vite-server': '4.6.4' } })) - await writeFile(join(cwd, 'nuxt.config.mjs'), `export default { server: { builder: 'vite' } }`) + it('should name a third-party server builder with its version', async () => { + await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', private: true, dependencies: { 'my-server-builder': '1.2.3' } })) + await writeFile(join(cwd, 'nuxt.config.mjs'), `export default { server: { builder: 'my-server-builder' } }`) + + expect((await runInfoJSON()).serverBuilder).toBe('my-server-builder@1.2.3') + }) + + it('should name a local server builder by its path alone', async () => { + await writeFile(join(cwd, 'package.json'), JSON.stringify({ name: 'app', version: '9.9.9', private: true })) + await writeFile(join(cwd, 'server-builder.mjs'), 'export async function bundle() {}') + await writeFile(join(cwd, 'nuxt.config.mjs'), `export default { server: { builder: './server-builder.mjs' } }`) - expect((await runInfoJSON()).serverBuilder).toMatch(/^@nuxt\/vite-server@\d/) + expect((await runInfoJSON()).serverBuilder).toBe('./server-builder.mjs') }) it('should still report on a project with no config', async () => { diff --git a/packages/nuxt-cli/test/unit/dev-tui.spec.ts b/packages/nuxt-cli/test/unit/dev-tui.spec.ts index 8398bd361..e28d89ca1 100644 --- a/packages/nuxt-cli/test/unit/dev-tui.spec.ts +++ b/packages/nuxt-cli/test/unit/dev-tui.spec.ts @@ -3017,16 +3017,16 @@ async function withPanel(run: (ui: ReturnType, settle: () => } describe('panel info view', () => { - it('lists Nitro until the app says another builder runs the server', async () => { + it('lists Nitro until the app names another server builder', async () => { await withPanel(async (_ui, settle) => { process.stdin.emit('keypress', 'i', { name: 'i', sequence: 'i' }) - expect(await settle()).toMatch(/Nitro\s+2\.13\.5/) + expect(await settle()).toMatch(/Server builder\s+Nitro 2\.13\.5/) }) await withPanel(async (ui, settle) => { - ui.setStack({ builder: 'vite', nitro: false }) + ui.setStack({ builder: 'vite', server: 'vite' }) process.stdin.emit('keypress', 'i', { name: 'i', sequence: 'i' }) const frame = await settle() - expect(frame).toContain('versions') + expect(frame).toMatch(/Server builder\s+Vite\b/) expect(frame).not.toContain('Nitro') }) }) diff --git a/packages/nuxt-cli/test/unit/dev/initialize.spec.ts b/packages/nuxt-cli/test/unit/dev/initialize.spec.ts index 780722d55..77550a640 100644 --- a/packages/nuxt-cli/test/unit/dev/initialize.spec.ts +++ b/packages/nuxt-cli/test/unit/dev/initialize.spec.ts @@ -152,4 +152,19 @@ describe('initialize', () => { expect(changed).toHaveBeenCalledTimes(1) expect(restarted).toHaveBeenCalledWith({ type: 'hook' }) }) + + it('should replay the latest stack and forward later ones', async () => { + const { onStack } = await initialize(context(), { captureUIEvents: true }) + devServers[0].emit('stack', { builder: 'vite', server: 'nitro' }) + devServers[0].emit('stack', { builder: 'vite', server: 'vite' }) + + const seen: unknown[] = [] + onStack(stack => seen.push(stack)) + devServers[0].emit('stack', { builder: 'webpack', server: 'nitro' }) + + expect(seen).toEqual([ + { builder: 'vite', server: 'vite' }, + { builder: 'webpack', server: 'nitro' }, + ]) + }) }) diff --git a/packages/nuxt-cli/test/unit/dev/lifecycle.spec.ts b/packages/nuxt-cli/test/unit/dev/lifecycle.spec.ts index 1b2aeeb3d..5e658c34a 100644 --- a/packages/nuxt-cli/test/unit/dev/lifecycle.spec.ts +++ b/packages/nuxt-cli/test/unit/dev/lifecycle.spec.ts @@ -276,6 +276,26 @@ describe('dev server request feed', () => { }) }) +describe('dev server stack', () => { + it('should report the server builder on load and again once it has built', async () => { + const nuxt = createNuxt({ builder: 'vite', server: { builder: { bundle: () => {} } } }) as FakeNuxt & { serverBuild?: unknown } + loadNuxt.mockImplementation(() => Promise.resolve(nuxt)) + buildNuxt.mockImplementation(async () => { + nuxt.serverBuild = { name: 'nitro', label: 'Nitro', capabilities: { server: true, dev: true } } + }) + const server = createServer({ captureUIEvents: true }) + const seen: unknown[] = [] + server.on('stack', stack => seen.push(stack)) + + await server.init() + + expect(seen).toEqual([ + { builder: 'vite', server: 'custom' }, + { builder: 'vite', server: 'nitro' }, + ]) + }) +}) + describe('dev server failures', () => { it('should surface a first-run failure to the caller', async () => { loadNuxt.mockImplementation(() => Promise.reject(new Error('config exploded'))) From 5bb102d2bd18fdd456aae60b392e5e224b4404fb Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 10 Nov 2026 16:40:39 +0000 Subject: [PATCH 6/6] feat(build,preview): label the deploy target as Deploy target --- packages/nuxt-cli/src/commands/build.ts | 2 +- packages/nuxt-cli/src/commands/preview.ts | 6 ++-- packages/nuxt-cli/src/utils/server-build.ts | 7 ----- .../nuxt-cli/test/unit/commands/build.spec.ts | 5 ++- .../test/unit/commands/preview.spec.ts | 31 +++++++++++++++++++ .../test/unit/utils/server-build.spec.ts | 2 +- 6 files changed, 37 insertions(+), 16 deletions(-) diff --git a/packages/nuxt-cli/src/commands/build.ts b/packages/nuxt-cli/src/commands/build.ts index 8fab2d26f..55b95a2fc 100644 --- a/packages/nuxt-cli/src/commands/build.ts +++ b/packages/nuxt-cli/src/commands/build.ts @@ -122,7 +122,7 @@ export default defineCommand({ const serverBuild = resolveServerBuild(kit, nuxt) const target = serverBuild.target if (target) { - logger.info(`${serverBuild.label} ${serverBuild.targetLabel}: ${styleText('cyan', target)}`) + logger.info(`Deploy target: ${styleText('cyan', target)}`) } const outputDir = serverBuild.dir diff --git a/packages/nuxt-cli/src/commands/preview.ts b/packages/nuxt-cli/src/commands/preview.ts index d1b3a0214..151d18919 100644 --- a/packages/nuxt-cli/src/commands/preview.ts +++ b/packages/nuxt-cli/src/commands/preview.ts @@ -73,7 +73,6 @@ const command = defineCommand({ let declared: { label: string target: string | undefined - targetLabel: string dir: string previewCommand: string | undefined staticDir: string | undefined @@ -106,7 +105,6 @@ const command = defineCommand({ declared = { label: build.label, target: build.target, - targetLabel: build.targetLabel, dir: build.dir, previewCommand: build.previewCommand, staticDir: build.previewStaticDir, @@ -162,7 +160,7 @@ const command = defineCommand({ previewCommand = declared.previewCommand outputPath = declared.dir if (declared.target) { - target = [`${declared.label} ${declared.targetLabel}:`, declared.target] + target = ['Deploy target:', declared.target] } } if (declared.staticDir) { @@ -184,7 +182,7 @@ const command = defineCommand({ previewCommand = nitroJSON.commands?.preview outputPath = dirname(nitroJSONPath) if (nitroJSON.preset) { - target = ['Nitro preset:', nitroJSON.preset] + target = ['Deploy target:', nitroJSON.preset] } } } diff --git a/packages/nuxt-cli/src/utils/server-build.ts b/packages/nuxt-cli/src/utils/server-build.ts index ff541573c..7d7795bd5 100644 --- a/packages/nuxt-cli/src/utils/server-build.ts +++ b/packages/nuxt-cli/src/utils/server-build.ts @@ -6,9 +6,6 @@ import { resolve } from 'pathe' const DEFAULT_OUTPUT_DIR = '.output' const DEFAULT_PUBLIC_DIR = '.output/public' -/** What a deploy target is called when the builder does not say. */ -const DEFAULT_TARGET_LABEL = 'preset' - /** * A server builder as the CLI sees it. Anything a builder can change during its * own init is a getter, so it is read when used rather than snapshotted. @@ -16,8 +13,6 @@ const DEFAULT_TARGET_LABEL = 'preset' export interface ServerBuild { /** The builder's display name, e.g. `Nitro` or `Vite SPA`. */ readonly label: string - /** What the builder calls its deploy target axis, e.g. `preset`. */ - targetLabel: string /** Whether Nuxt described this build itself, rather than the CLI inferring it. */ declared: boolean /** Whether this builder can serve `nuxt dev`. */ @@ -39,7 +34,6 @@ interface NuxtServerBuild { name: string label?: string target?: () => string | undefined - targetLabel?: string output: { dir: () => string, publicDir: () => string } capabilities: { server: boolean, dev: boolean } preview?: { @@ -150,7 +144,6 @@ export function resolveServerBuild(kit: MaybeModernKit, nuxt: Nuxt): ServerBuild const getNitro = () => declared?.capabilities.server === false ? undefined : tryUseNitro(kit) return { - targetLabel: declared?.targetLabel ?? DEFAULT_TARGET_LABEL, declared: !!declared, hasDevServer: declared?.capabilities.dev ?? true, get label() { diff --git a/packages/nuxt-cli/test/unit/commands/build.spec.ts b/packages/nuxt-cli/test/unit/commands/build.spec.ts index d39d27c77..0b5bf8ef0 100644 --- a/packages/nuxt-cli/test/unit/commands/build.spec.ts +++ b/packages/nuxt-cli/test/unit/commands/build.spec.ts @@ -157,7 +157,7 @@ describe('build', () => { })) }) - it('names the deploy target as the builder does', async () => { + it('names the deploy target the server builder declares', async () => { mocks.buildNuxt.mockResolvedValue(undefined) mocks.loadNuxt.mockResolvedValue({ hook: vi.fn(), @@ -175,8 +175,7 @@ describe('build', () => { await run() - expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('Nitro preset:')) - expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('vercel')) + expect(logger.info).toHaveBeenCalledWith(expect.stringMatching(/^Deploy target: .*vercel/)) }) it('builds without a server, locking the default output directory', async () => { diff --git a/packages/nuxt-cli/test/unit/commands/preview.spec.ts b/packages/nuxt-cli/test/unit/commands/preview.spec.ts index ff8bd0168..d990f2dc9 100644 --- a/packages/nuxt-cli/test/unit/commands/preview.spec.ts +++ b/packages/nuxt-cli/test/unit/commands/preview.spec.ts @@ -11,6 +11,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import preview from '../../../src/commands/preview' import { previewLockDir, readLock } from '../../../src/utils/lockfile' import { logger } from '../../../src/utils/logger' +import { render, screen } from '../../utils/terminal' const { checkPort, getPort, loadKit, loadNuxt, x } = vi.hoisted(() => ({ checkPort: vi.fn<(port: number, host?: string) => Promise>(), @@ -129,6 +130,36 @@ describe('preview', () => { }) }) + it('names the deploy target a Nitro build recorded', async () => { + await writeNitroJSON(join(cwd, '.output'), { preset: 'cloudflare-module' }) + + const terminal = await render(() => runCommand(preview, { rawArgs: [cwd] })) + + expect(screen(terminal)).toMatch(/Deploy target:\s+cloudflare-module/) + }) + + it('names the deploy target the server builder declares', async () => { + const outputDir = join(cwd, '.output') + await mkdir(outputDir, { recursive: true }) + loadNuxt.mockResolvedValue({ + options: { srcDir: cwd }, + hook: vi.fn(), + close: vi.fn(), + serverBuild: { + name: 'vite', + label: 'Vite server', + target: () => 'cloudflare', + capabilities: { server: true, dev: true }, + output: { dir: () => outputDir, publicDir: () => join(outputDir, 'public') }, + preview: { command: () => 'npx wrangler dev' }, + }, + }) + + const terminal = await render(() => runCommand(preview, { rawArgs: [cwd] })) + + expect(screen(terminal)).toMatch(/Deploy target:\s+cloudflare/) + }) + it('uses the configured output directory', async () => { const outputDir = join(cwd, 'dist', 'server-output') await writeNitroJSON(outputDir) diff --git a/packages/nuxt-cli/test/unit/utils/server-build.spec.ts b/packages/nuxt-cli/test/unit/utils/server-build.spec.ts index befd0fedd..5639229f9 100644 --- a/packages/nuxt-cli/test/unit/utils/server-build.spec.ts +++ b/packages/nuxt-cli/test/unit/utils/server-build.spec.ts @@ -91,7 +91,7 @@ describe('resolveServerBuild', () => { }) const build = resolveServerBuild(nitroKit, nuxt) - expect(build).toMatchObject({ label: 'Nitro', targetLabel: 'preset', declared: true, hasDevServer: true }) + expect(build).toMatchObject({ label: 'Nitro', declared: true, hasDevServer: true }) expect(build.target).toBe('vercel') expect(build.previewCommand).toBe('node ./server/index.mjs') expect(build.dir).toBe('/project/.vercel/output')