From 13a84acc8aef8f5aa994f8be6c8093df6292768c Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 16 Mar 2026 17:23:25 +0800 Subject: [PATCH] fix(create): normalize target directory to forward slashes on Windows On Windows, `path.join` and `path.normalize` produce backslash-separated paths (e.g., `packages\core`). When passed as CLI arguments to external commands like `create-vite`, the backslash gets lost, creating `packagescore` instead of `packages/core`. Normalize all relative target directory paths to forward slashes at the source: in `formatTargetDir()` and in `bin.ts` path joins. Closes #938 --- packages/cli/src/create/__tests__/utils.spec.ts | 14 +++++++++++++- packages/cli/src/create/bin.ts | 4 ++-- packages/cli/src/create/utils.ts | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/create/__tests__/utils.spec.ts b/packages/cli/src/create/__tests__/utils.spec.ts index 3f7e70503b..12d75e0e5b 100644 --- a/packages/cli/src/create/__tests__/utils.spec.ts +++ b/packages/cli/src/create/__tests__/utils.spec.ts @@ -17,7 +17,8 @@ describe('formatTargetDir', () => { expect(formatTargetDir('../../foo/bar')).matchSnapshot(); }); - it.skipIf(process.platform === 'win32')('should format target dir with valid input', () => { + // Should work on all platforms (including Windows) - directory must always use forward slashes + it('should format target dir with valid input', () => { expect(formatTargetDir('./my-package')).matchSnapshot(); expect(formatTargetDir('my-package')).matchSnapshot(); expect(formatTargetDir('@my-scope/my-package')).matchSnapshot(); @@ -28,6 +29,17 @@ describe('formatTargetDir', () => { expect(formatTargetDir('./foo/bar/@scope/my-package/sub-package')).matchSnapshot(); }); + // Regression test for https://github.com/voidzero-dev/vite-plus/issues/938 + // On Windows, path.join/normalize produce backslashes which break when passed as CLI args. + // Nested paths are the critical cases since they involve path separators. + it('should always use forward slashes in directory (issue #938)', () => { + expect(formatTargetDir('foo/@my-scope/my-package').directory).toBe('foo/my-package'); + expect(formatTargetDir('./foo/bar/@scope/my-package').directory).toBe('foo/bar/my-package'); + expect(formatTargetDir('./foo/bar/@scope/my-package/sub-package').directory).toBe( + 'foo/bar/@scope/my-package/sub-package', + ); + }); + it('should format target dir with invalid package name', () => { expect(formatTargetDir('my-package@').error).matchSnapshot(); expect(formatTargetDir('my-package@1.0.0').error).matchSnapshot(); diff --git a/packages/cli/src/create/bin.ts b/packages/cli/src/create/bin.ts index a9bb79f316..dd7d513cea 100644 --- a/packages/cli/src/create/bin.ts +++ b/packages/cli/src/create/bin.ts @@ -584,7 +584,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h const selected = await promptPackageNameAndTargetDir(defaultPackageName, options.interactive); packageName = selected.packageName; targetDir = selectedParentDir - ? path.join(selectedParentDir, selected.targetDir) + ? path.join(selectedParentDir, selected.targetDir).split(path.sep).join('/') : selected.targetDir; } } @@ -782,7 +782,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h const selected = await promptPackageNameAndTargetDir(defaultPackageName, options.interactive); packageName = selected.packageName; targetDir = templateInfo.parentDir - ? path.join(templateInfo.parentDir, selected.targetDir) + ? path.join(templateInfo.parentDir, selected.targetDir).split(path.sep).join('/') : selected.targetDir; } pauseCreateProgress(); diff --git a/packages/cli/src/create/utils.ts b/packages/cli/src/create/utils.ts index dbe335776a..cf19781f83 100644 --- a/packages/cli/src/create/utils.ts +++ b/packages/cli/src/create/utils.ts @@ -85,7 +85,7 @@ export function formatTargetDir(input: string): { error: `Parsed package name "${packageName}" is invalid: ${message}`, }; } - return { directory: targetDir, packageName }; + return { directory: targetDir.split(path.sep).join('/'), packageName }; } // Get the project directory from the project name