Skip to content

fix(create): avoid following target symlinks - #2418

Open
leslieeilsel wants to merge 3 commits into
voidzero-dev:mainfrom
leslieeilsel:agent/one-line-challenge
Open

fix(create): avoid following target symlinks#2418
leslieeilsel wants to merge 3 commits into
voidzero-dev:mainfrom
leslieeilsel:agent/one-line-challenge

Conversation

@leslieeilsel

@leslieeilsel leslieeilsel commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #2419

Summary

  • classify the target entry with lstatSync instead of following the final symbolic link
  • identify symbolic links and other non-directory conflicts explicitly in the overwrite prompt
  • remove only the target link or file while preserving linked-directory contents
  • retain the existing cleanup behavior for real directories, including preserving .git

Root cause

The availability check used existsSync followed by readdirSync, so a directory symbolic link was treated as the directory it referenced. The cleanup loop then removed child paths through that link.

existsSync also treated dangling links as missing, while existing regular files reached directory-only operations.

Implementation

The target is now inspected with:

fs.lstatSync(targetPath, { throwIfNoEntry: false })

This keeps the final filesystem entry distinct from its destination:

  • a real empty directory remains immediately reusable
  • a real non-empty directory keeps the existing confirmation and cleanup behavior
  • a symbolic link receives an explicit “Remove symbolic link and continue” confirmation
  • confirmed links and regular files are removed as entries, without recursive traversal
  • dangling symbolic links are recognized as existing conflicts and can be removed safely

The cleanup function performs a fresh lstatSync immediately before mutation, so it also handles a target that disappears while the prompt is open.

Validation

  • vitest run packages/cli/src/create/__tests__: 13 files, 185 tests passed
  • tsc -p packages/cli/tsconfig.json --noEmit
  • targeted Vite+ lint and format checks
  • git diff --check

Regression coverage includes real directories, links to empty and populated directories, dangling links, regular files, linked-target preservation, and the existing .git preservation behavior.

@netlify

netlify Bot commented Aug 11, 2026

Copy link
Copy Markdown

Deploy Preview for viteplus-preview canceled.

Name Link
🔨 Latest commit 2085563
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a7bfc3b5702eb0008b97d74

@leslieeilsel
leslieeilsel marked this pull request as ready for review August 12, 2026 04:33

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5de4880171

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/cli/src/create/prompts.ts Outdated
Comment on lines +108 to +109
const stats = fs.lstatSync(projectDirFullPath, { throwIfNoEntry: false });
if (!stats || (stats.isDirectory() && isEmpty(projectDirFullPath))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove trailing separators before calling lstat

When the requested target ends in a separator (for example, vp create vite:application --directory linked-project/), path.normalize preserves that separator and POSIX lstatSync("linked-project/") dereferences the final symlink. The link is therefore classified as a real directory, and after the user confirms the ordinary overwrite prompt, clearTargetPath traverses the link and deletes files in its destination—the data-loss case this change is intended to prevent. Strip trailing separators (while preserving filesystem roots) before every classification and cleanup operation, and cover this input in the symlink regression tests.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

create: overwrite follows target symlinks and deletes linked contents

1 participant