diff --git a/packages/vite/src/node/__tests__/config.spec.ts b/packages/vite/src/node/__tests__/config.spec.ts index 0dcd4645745d85..715ef239e4d939 100644 --- a/packages/vite/src/node/__tests__/config.spec.ts +++ b/packages/vite/src/node/__tests__/config.spec.ts @@ -1319,6 +1319,64 @@ describe('resolveConfig', () => { } }) + // https://github.com/vitejs/vite/issues/23383 + test('preserves Windows input paths with glob-special segment names', async () => { + const cases: { + name: string + input: UserConfig['input'] + expected: UserConfig['input'] + }[] = [ + ...(isWindows + ? [ + { + name: 'segment starting with @', + input: 'D:\\desk\\test\\@src\\whatever.html', + expected: 'D:\\desk\\test\\@src\\whatever.html', + }, + { + name: 'segment starting with !', + input: 'D:\\desk\\test\\!notes\\page.html', + expected: 'D:\\desk\\test\\!notes\\page.html', + }, + { + name: 'segment starting with +', + input: 'D:\\desk\\test\\+draft\\page.html', + expected: 'D:\\desk\\test\\+draft\\page.html', + }, + { + name: 'segment starting with (', + input: 'D:\\desk\\test\\(draft)\\page.html', + expected: 'D:\\desk\\test\\(draft)\\page.html', + }, + { + name: 'segment starting with [', + input: 'D:\\desk\\test\\[id]\\page.html', + expected: 'D:\\desk\\test\\[id]\\page.html', + }, + { + name: 'relative path', + input: 'test\\@src\\whatever.html', + expected: 'test\\@src\\whatever.html', + }, + { + name: 'escaped glob on windows', + input: 'D:/desk/test/\\[id]\\page.html', + expected: 'D:/desk/test/[id]\\page.html', + }, + ] + : []), + ] + + for (const { name, input, expected } of cases) { + await expect( + resolveConfig({ input }, 'serve'), + name, + ).resolves.toMatchObject({ + input: expected, + }) + } + }) + test('allows non-glob special characters in input', async () => { const cases: { name: string diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index e350b1c3e82d76..5f195eaa15d613 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -33,7 +33,7 @@ import { createImportMetaResolver, importMetaResolveWithCustomHookString, } from '../module-runner/importMetaResolver' -import { withTrailingSlash } from '../shared/utils' +import { withTrailingSlash, isWindows } from '../shared/utils' import type { AnymatchFn } from '../types/anymatch' import type { HtmlAssetSource } from './assetSource' import { PartialEnvironment } from './baseEnvironment' @@ -945,10 +945,14 @@ function normalizeInput( return resolved } -const escapedGlobCharactersRE = /\\([*?[\]{}()!+@|])/g +const escapedGlobCharactersRE = /\\([*?[\]{}()|]|[@+!](?=\())/g +function hasUnescapedGlob(value: string): boolean { + return isDynamicPattern(value.replace(escapedGlobCharactersRE, '')) +} function unescapeGlobCharacters(value: string): string { - if (isDynamicPattern(value)) { + if (isWindows) return value + if (hasUnescapedGlob(value)) { // so that it could later be changed to accept globs without a breaking change throw new Error( `\`input\` cannot contain glob characters. They are reserved, ` +