镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Commit 3667cf8

Browse files
hi-ogawaclaude
andauthored
fix: support api.port 0 (#11641)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 34b44a3 commit 3667cf8

6 files changed

Lines changed: 38 additions & 20 deletions

File tree

‎docs/config/api.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Listen to port and serve API for [the UI](/guide/ui) or [browser server](/guide/
1313

1414
In Browser Mode the server binds its port on the first browser launch, unless `api` or `ui` is enabled.
1515

16+
Set `port` to `0` to let the operating system pick a free port.
17+
1618
## api.allowWrite <Version>4.1.0</Version> {#api-allowwrite}
1719

1820
- **Type:** `boolean`

‎packages/vitest/src/node/config/resolveConfig.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export function resolveApiServerConfig(
120120
}
121121

122122
if (api) {
123-
if (!api.port && !api.middlewareMode) {
123+
if (api.port == null && !api.middlewareMode) {
124124
api.port = defaultPort
125125
}
126126
} else {
@@ -132,7 +132,7 @@ export function resolveApiServerConfig(
132132
api.middlewareMode = false
133133
// The browser server is standalone, so it always needs a port even when the
134134
// user didn't configure `api`/`ui` (in which case `api` defaulted above)
135-
if (!api.port) {
135+
if (api.port == null) {
136136
api.port = defaultPort
137137
}
138138
}

‎packages/vitest/src/node/logger.ts‎

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -237,22 +237,16 @@ export class Logger {
237237
this.log(PAD + c.gray(`Running tests with seed "${seed}"`))
238238
}
239239

240-
if (this.ctx.config.ui) {
241-
const host = this.ctx.config.api?.host || 'localhost'
242-
const port = this.ctx.vite.config.server.port
243-
const url = new URL(this.ctx.config.uiBase, `http://${host}:${port}`)
244-
url.searchParams.set('token', this.ctx.config.api.token)
245-
246-
this.log(PAD + c.dim(c.green(`UI started at ${url}`)))
247-
}
248-
// browser mode always has `api.port`, but only listens on startup for `api`/`ui`
249-
else if (this.ctx.config.api?.port && this.ctx.config._apiRequested) {
250-
const resolvedUrls = this.ctx.vite.resolvedUrls
251-
// workaround for https://github.com/vitejs/vite/issues/15438, it was fixed in vite 5.1
252-
const fallbackUrl = `http://${this.ctx.config.api.host || 'localhost'}:${this.ctx.config.api.port}`
253-
const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0] ?? fallbackUrl
254-
255-
this.log(PAD + c.dim(c.green(`API started at ${new URL('/', origin)}`)))
240+
const resolvedUrls = this.ctx.vite.resolvedUrls
241+
const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0]
242+
if (origin && this.ctx.config._apiRequested) {
243+
if (this.ctx.config.ui) {
244+
const url = new URL(this.ctx.config.uiBase, origin)
245+
url.searchParams.set('token', this.ctx.config.api.token)
246+
this.log(PAD + c.dim(c.green(`UI started at ${url}`)))
247+
} else {
248+
this.log(PAD + c.dim(c.green(`API started at ${new URL('/', origin)}`)))
249+
}
256250
}
257251

258252
if (this.ctx.coverageProvider) {

‎packages/vitest/src/node/plugins/browserLoader.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export async function createClusterServer(
173173

174174
if (!contribution) {
175175
const server = await createViteServer(viteConfig)
176-
if (config.api.port) {
176+
if (config.api.port != null) {
177177
await server.listen(config.api.port)
178178
}
179179
return { server }

‎test/browser/specs/server-url.test.ts‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { afterEach, expect, test } from 'vitest'
2-
import { instances, runBrowserTests } from './utils'
2+
import { defaultBrowserPort } from 'vitest/config'
3+
import { instances, runBrowserTests, runInlineBrowserTests } from './utils'
34

45
afterEach(() => {
56
delete process.env.TEST_HTTPS
@@ -28,3 +29,15 @@ test('server-url https', async () => {
2829
expect(new URL(url).port).toBe('31122')
2930
expect(stdout).toReportSummaryTestFiles({ passed: instances.length })
3031
})
32+
33+
test('server-url port 0', async () => {
34+
const { stderr, ctx } = await runInlineBrowserTests(
35+
{ 'basic.test.ts': `test('basic', () => {})` },
36+
{ api: { port: 0 }, globals: true, watch: true },
37+
)
38+
expect(stderr).toBe('')
39+
const url = ctx?.projects[0].vite.resolvedUrls?.local[0]
40+
expect.assert(url)
41+
// Vite 8.2.1+ binds an OS-assigned port, older Vite binds 5173
42+
expect(new URL(url).port).not.toBe(String(defaultBrowserPort))
43+
})

‎test/e2e/test/override.test.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ describe('correctly defines api flag', () => {
5050
})
5151
})
5252

53+
it.for<[string, Parameters<typeof config>[0]]>([
54+
['api.port', { api: { port: 0 } }],
55+
['api', { api: 0 }],
56+
])('keeps port 0 with %s', async ([, options]) => {
57+
const c = await config(options)
58+
expect(c.api.port).toBe(0)
59+
expect(c.$viteConfig.server.port).toBe(0)
60+
})
61+
5362
it('allowWrite and allowExec default to true when not exposed to network', async () => {
5463
const c = await config({ api: { port: 5555 } })
5564
expect(c.api.allowWrite).toBe(true)

0 commit comments

Comments
 (0)