diff --git a/packages/nuxt-cli/src/commands/preview.ts b/packages/nuxt-cli/src/commands/preview.ts index bbad85f76..5c157a540 100644 --- a/packages/nuxt-cli/src/commands/preview.ts +++ b/packages/nuxt-cli/src/commands/preview.ts @@ -143,9 +143,10 @@ const command = defineCommand({ target = ['Nitro preset:', nitroJSON.preset] } } - staticDirs.push(resolve(cwd, '.output', 'public')) } + staticDirs.push(resolve(cwd, '.output', 'public')) + if (typeof previewCommand !== 'string' || !previewCommand.trim()) { // A build with no server runtime leaves only static files, which the CLI // can serve itself rather than reporting a missing server entry. diff --git a/packages/nuxt-cli/src/utils/server-build.ts b/packages/nuxt-cli/src/utils/server-build.ts index f3739a657..8f853fdac 100644 --- a/packages/nuxt-cli/src/utils/server-build.ts +++ b/packages/nuxt-cli/src/utils/server-build.ts @@ -108,11 +108,9 @@ export function tryUseNitro(kit: MaybeModernKit): NitroLike | undefined { */ export function getServerBuilderName(nuxt: Nuxt, hasServer?: boolean): string { const declared = (nuxt as MaybeModernNuxt).serverBuild - if (declared) { - return declared.label || declared.name - } - - const name = inferBuilderName(nuxt) + // 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) if (name === 'nitro') { return 'Nitro' } @@ -130,11 +128,16 @@ export function getServerBuilderName(nuxt: Nuxt, hasServer?: boolean): string { function inferBuilderName(nuxt: Nuxt): string | undefined { const builder = (nuxt as MaybeModernNuxt).options.server?.builder if (typeof builder === 'string' && builder) { - return builder.replace(/^@nuxt\//, '').replace(/-server$/, '') + return normalizeBuilderName(builder) } return builder ? 'custom' : undefined } +/** A module specifier such as `@nuxt/vite-server` read as a builder name. */ +function normalizeBuilderName(specifier: string): string { + return specifier.replace(/^@nuxt\//, '').replace(/-server$/, '') +} + /** * A directory path that compares equal to another naming the same directory. * @@ -163,7 +166,7 @@ export function resolveServerBuild(kit: MaybeModernKit, nuxt: Nuxt): ServerBuild declared: !!declared, hasDevServer: declared?.capabilities.dev ?? true, get name() { - return declared?.name ?? inferBuilderName(nuxt) ?? (getNitro() ? 'nitro' : 'unknown') + return (declared ? normalizeBuilderName(declared.name) : undefined) ?? inferBuilderName(nuxt) ?? (getNitro() ? 'nitro' : 'unknown') }, get label() { return getServerBuilderName(nuxt, !!getNitro()) @@ -172,7 +175,7 @@ export function resolveServerBuild(kit: MaybeModernKit, nuxt: Nuxt): ServerBuild return declared?.capabilities.server ?? !!getNitro() }, get target() { - return declared ? declared.target?.() : getNitro()?.options.preset + return declared?.target?.() ?? getNitro()?.options.preset }, get dir() { return normalizeDir(declared?.output.dir() @@ -185,7 +188,7 @@ export function resolveServerBuild(kit: MaybeModernKit, nuxt: Nuxt): ServerBuild ?? resolve(nuxt.options.rootDir, nuxt.options.nitro?.output?.publicDir || DEFAULT_PUBLIC_DIR)) }, get previewCommand() { - return declared ? declared.preview?.command?.() : getNitro()?.options.commands?.preview + return declared?.preview?.command?.() ?? getNitro()?.options.commands?.preview }, get previewStaticDir() { const dir = declared?.preview?.staticDir?.() 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 239afaf97..35f21b750 100644 --- a/packages/nuxt-cli/test/unit/utils/server-build.spec.ts +++ b/packages/nuxt-cli/test/unit/utils/server-build.spec.ts @@ -55,6 +55,11 @@ describe('getServerBuilderName', () => { expect(getServerBuilderName(makeNuxt(), false)).toBe('unknown') }) + it('should read an unresolved descriptor name as a builder name', () => { + const nuxt = makeNuxt({}, { serverBuild: descriptor({ name: '@nuxt/nitro-server' }) }) + expect(getServerBuilderName(nuxt)).toBe('Nitro') + }) + it('should use the label Nuxt declares', () => { const nuxt = makeNuxt({ server: { builder: 'vite' } }, { serverBuild: descriptor({ name: 'vite', label: 'Vite SPA' }) }) expect(getServerBuilderName(nuxt)).toBe('Vite SPA') @@ -118,6 +123,26 @@ describe('resolveServerBuild', () => { expect(useNitro).not.toHaveBeenCalled() }) + it('should fall back to Nitro for a descriptor that declares no target or preview command', () => { + const nuxt = makeNuxt({}, { serverBuild: descriptor({ name: '@nuxt/nitro-server' }) }) + const build = resolveServerBuild(nitroKit, nuxt) + + expect(build).toMatchObject({ name: 'nitro', label: 'Nitro', declared: true, hasServer: true }) + expect(build.target).toBe('node-server') + expect(build.previewCommand).toBe('node ./server/index.mjs') + expect(build.previewStaticDir).toBeUndefined() + }) + + it('should fall back to Nitro for a descriptor whose getters resolve to nothing', () => { + const nuxt = makeNuxt({}, { + serverBuild: descriptor({ target: () => undefined, preview: { command: () => undefined } }), + }) + const build = resolveServerBuild(nitroKit, nuxt) + + expect(build.target).toBe('node-server') + expect(build.previewCommand).toBe('node ./server/index.mjs') + }) + it('should report a builder that declares no dev server', () => { const nuxt = makeNuxt({}, { serverBuild: descriptor({ capabilities: { server: true, dev: false } }) }) expect(resolveServerBuild(nitroKit, nuxt).hasDevServer).toBe(false)