Skip to content

Commit 682c773

Browse files
committed
fix(dev): omit default ports and decode urls in the dev url block
1 parent 62a030f commit 682c773

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

packages/nuxt-cli/src/dev/listen.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,24 @@ export interface Listener {
6767

6868
const ANY_HOSTS = new Set(['', '0.0.0.0', '::'])
6969
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1'])
70+
const DEFAULT_PORTS = { http: 80, https: 443 } as const
71+
72+
/**
73+
* Render a URL for display: the port is omitted when it is the protocol
74+
* default, and percent-encoding is decoded so a non-ASCII `baseURL` is
75+
* readable. A malformed sequence is left as-is rather than throwing.
76+
*/
77+
export function formatDisplayURL(protocol: 'http' | 'https', host: string, port: number, baseURL: string): string {
78+
const hostname = host.includes(':') ? `[${host}]` : host
79+
const suffix = port === DEFAULT_PORTS[protocol] ? '' : `:${port}`
80+
const url = `${protocol}://${hostname}${suffix}${baseURL}`
81+
try {
82+
return decodeURI(url)
83+
}
84+
catch {
85+
return url
86+
}
87+
}
7088

7189
const HOSTNAME_RE = /^(?!-)[\d.:a-z-]{1,253}(?<!-)$/i
7290

@@ -152,7 +170,7 @@ export async function listen(handler: RequestListener, options: ListenOptions =
152170
const address = server.address() as AddressInfo
153171
const protocol = certificate ? 'https' : 'http'
154172
const baseURL = options.baseURL || '/'
155-
const formatURL = (host: string) => `${protocol}://${host.includes(':') ? `[${host}]` : host}:${address.port}${baseURL}`
173+
const formatURL = (host: string) => formatDisplayURL(protocol, host, address.port, baseURL)
156174

157175
const anyHost = ANY_HOSTS.has(hostname)
158176
const url = formatURL(anyHost ? 'localhost' : hostname)

packages/nuxt-cli/test/unit/listen.spec.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { networkInterfaces } from 'node:os'
44

55
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
66

7-
import { copyURL, getNetworkAddresses, listen, openBrowser, resolveOpenCommand, validateHostname } from '../../src/dev/listen'
7+
import { copyURL, formatDisplayURL, getNetworkAddresses, listen, openBrowser, resolveOpenCommand, validateHostname } from '../../src/dev/listen'
88

99
const writeText = vi.hoisted(() => vi.fn())
1010

@@ -62,6 +62,31 @@ describe('getNetworkAddresses', () => {
6262
})
6363
})
6464

65+
describe('formatDisplayURL', () => {
66+
it('should include a non-default port', () => {
67+
expect(formatDisplayURL('http', 'localhost', 3000, '/')).toBe('http://localhost:3000/')
68+
expect(formatDisplayURL('https', 'localhost', 8443, '/')).toBe('https://localhost:8443/')
69+
})
70+
71+
it('should omit the default port for the protocol', () => {
72+
expect(formatDisplayURL('http', 'localhost', 80, '/')).toBe('http://localhost/')
73+
expect(formatDisplayURL('https', 'localhost', 443, '/')).toBe('https://localhost/')
74+
expect(formatDisplayURL('https', 'localhost', 80, '/')).toBe('https://localhost:80/')
75+
})
76+
77+
it('should bracket ipv6 hosts', () => {
78+
expect(formatDisplayURL('http', '::1', 3000, '/')).toBe('http://[::1]:3000/')
79+
})
80+
81+
it('should decode a percent-encoded base url', () => {
82+
expect(formatDisplayURL('http', 'localhost', 3000, '/%C3%A9t%C3%A9/')).toBe('http://localhost:3000/été/')
83+
})
84+
85+
it('should leave a malformed encoding alone', () => {
86+
expect(formatDisplayURL('http', 'localhost', 3000, '/%E0%A4%A/')).toBe('http://localhost:3000/%E0%A4%A/')
87+
})
88+
})
89+
6590
describe('validateHostname', () => {
6691
it('should pass through valid hosts', () => {
6792
expect(validateHostname('localhost')).toBe('localhost')

0 commit comments

Comments
 (0)