Skip to content

fix(create-bestax): never empty a non-empty folder under -y, and validate flags first - #954

Merged
allxsmith merged 3 commits into
mainfrom
fix/945-yes-overwrite
Oct 8, 2026
Merged

allxsmith merged 3 commits into
mainfrom
fix/945-yes-overwrite

Conversation

@allxsmith

@allxsmith allxsmith commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

-y emptied an existing folder without asking, and did it before the other flags were checked. So -t vite-typescript -y emptied the folder and then failed on the template, leaving neither the user's files nor a scaffold. Without a terminal, the message for a non-empty folder suggested -y as the way through. In the interactive flow, answering yes to "Remove existing files and continue?" emptied the folder right away, so a Ctrl+C at a later prompt left it empty.

Every flag value is now validated before any prompt runs or anything on disk changes. -y no longer deletes anything. A non-empty target folder under -y, or with no terminal to ask in, stops the run with exit code 1, lists what the folder holds, and points at the new --overwrite flag. The flag takes create-vite's name and description, since create-next-app has no override at all, and it also answers the interactive question. An interactive yes is now only recorded, and the folder is emptied after the last prompt, so a Ctrl+C anywhere before that leaves the files alone.

This is a behavior change to -y, made because it was losing people's files. It ships as a patch on purpose, since the old behavior was a data-loss bug rather than a contract. A script that relied on -y to empty a folder now needs --overwrite. The --help text, the README and the package's CLAUDE.md say so. The agent-loop eval's run-iteration.sh scaffolds with -y into a work folder its README expects to be fresh per run; a reused one now stops with the refusal instead of being emptied silently.

Two items from #950 live in the same flow and are fixed here. Ctrl+C at the skills prompt now cancels like every other prompt, where before it was read as "no" and the app was scaffolded without skills. The package.json name is now lower-cased the way npm init does it and loses a leading underscore the way create-vite does it, since npm rejects both. The folder keeps the name as typed, and the CLI prints the package name when it differs. A name that would leave nothing, such as one made only of underscores, is rejected. A project name that matches an existing file now stops with a plain message instead of a stack trace, and the interactive overwrite question lists what the folder holds, as the -y refusal does.

The other #950 items aren't in this PR: flags under bun create and the CLI's runtime dependency on the library.

pnpm all passes locally.

Fixes #945
Refs #950

…date flags first

-y used to empty an existing folder without asking, before the other flags were checked, so
a typo in -t left the folder empty with no scaffold. Every flag is now validated before any
prompt runs or anything on disk changes. A non-empty folder under -y, or with no terminal to
ask in, stops the run with exit code 1 and lists what is there. The new --overwrite flag,
named after create-vite's, is what removes the files. Accepting the interactive "Remove
existing files" question now only records the answer, and the folder is emptied after the
last prompt, so a later Ctrl+C leaves it alone.

This is a change to -y. A script that relied on it to empty a folder now needs --overwrite,
and the help text and README say so.

Two items from #950 live in the same flow. Ctrl+C at the skills prompt now cancels like every
other prompt instead of reading as "no". The package.json name is lower-cased and loses a
leading underscore, the way npm init and create-vite derive one, since npm rejects both.

Fixes #945
Refs #950
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 44 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: allxsmith/bestax/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3c8f2871-9899-474f-9520-f809be5f7365
📥 Commits

Reviewing files that changed from the base of the PR and between 90499a7 and 39b6232.

📒 Files selected for processing (14)
  • create-bestax/CLAUDE.md
  • create-bestax/README.md
  • create-bestax/src/__tests__/cli.test.ts
  • create-bestax/src/__tests__/constants.test.ts
  • create-bestax/src/__tests__/file-system.test.ts
  • create-bestax/src/__tests__/project-creator.test.ts
  • create-bestax/src/__tests__/prompts.test.ts
  • create-bestax/src/__tests__/validators.test.ts
  • create-bestax/src/cli.ts
  • create-bestax/src/constants.ts
  • create-bestax/src/file-system.ts
  • create-bestax/src/project-creator.ts
  • create-bestax/src/prompts.ts
  • create-bestax/src/validators.ts
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://638c81e0.bestax.pages.dev

@allxsmith

Copy link
Copy Markdown
Owner Author

deep-review: fresh

The previous run was cancelled at the runner limit before it posted anything, so this asks for the same full review again. The behavior change lives in create-bestax/src/project-creator.ts, cli.ts, validators.ts, file-system.ts and prompts.ts: the order of flag validation, prompting and emptying the folder, the -y and --overwrite paths, and the package-name normalization. Start there, then the tests that hold each path, then the README and CLAUDE.md wording.

Comment thread create-bestax/README.md
Comment thread create-bestax/src/file-system.ts
Comment thread create-bestax/src/project-creator.ts Outdated

@claude claude 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.

Deep review — 1 blocking · 2 advisory

# Severity Area Finding Location
1 🟡 Minor API Documented break to -y releases as a patch; no BREAKING CHANGE: footer, though release.config.js already has a breaking → major rule create-bestax/README.md:98
2 🔵 Advisory Robustness A target path that exists as a file throws a raw ENOTDIR out of create() — pre-existing shape, carried into the new helper create-bestax/src/file-system.ts:23
3 🔵 Advisory Robustness The interactive "Remove existing files and continue?" prompt still does not list the files, though entries is in hand and the refusal path lists them create-bestax/src/project-creator.ts:121

Overall: The change is sound and the ordering is right — validate flags → resolve and validate the name → decide about the directory → ask everything → then empty. I built create-bestax/dist from this branch and ran #945’s two repros plus seven adjacent paths against the real binary: demo -y and demo -t vite-typescript -y both leave .gitdir, notes.txt, src untouched and exit 1; -t vite-typescript -y --overwrite also refuses before deleting anything; --overwrite with every flag and no TTY empties then scaffolds; a non-empty directory with no TTY and no --overwrite gets the --overwrite refusal rather than the -y-suggesting NO_TTY text; MyApp→myapp, _under→under, My.App→my.app, and _/___ are rejected. 302 unit tests pass, coverage 98.91% / 92.55% branches, lint and typecheck clean. The riskiest part is not the code — it is the release type (finding 1); look at that first, because it is the one thing neither this review nor a fix agent can set.

Residual risk:

  • --overwrite can still empty-then-fail. validateOptions checks the template name, not that getTemplatePath(template) exists, so on a partial install copyDirectory throws TEMPLATE_NOT_FOUND after emptyDirectory. Open, but low: files in package.json ships templates, both template directories are in the tarball, and --overwrite is explicit consent to the delete. The un-consented version of this shape (-y alone) is gone.
  • -y callers inside this repo. Refuted. create-bestax/e2e/utils/scaffold.ts:44-47 removes outputDir before scaffolding, so the directory is always missing and the plan is "empty". eval/agent-loop/bin/run-batch.sh:67 clears its work dir per run. A lone run-iteration.sh with a reused work-dir now fails instead of silently emptying the app folder, which is what that script’s own set -euo pipefail and its "a broken scaffold is INFRA failure" comment ask for.
  • Names that survive normalization. Refuted for the cases that matter: only strings of _/. (never leading .) normalize to empty, and validateProjectName now rejects those. _- becomes package name -, which validate-npm-package-name accepts (no leading ./_, lowercase, URL-safe), and mid-name _/. are legal npm characters — so keeping them, rather than following create-vite’s collapse of every non-alphanumeric to a dash, is a deliberate difference and not a gap.
  • A partially-emptied directory. Refuted. emptyDirectory is now reached from exactly one place (project-creator.ts:608), after the last scaffold prompt, and there is no other emptyDir/remove call anywhere in src/.
  • Listing surfaces. Refuted. docs/docs/guides/distribution.md’s "What goes stale" has no bullet for create-bestax flags, and no docs page carries a CLI options table — the README and --help are the only two, and both changed here.
  • Not a risk, but worth knowing: the telemetry question is asked after the scaffold, so it lands after the emptying. The README’s "nothing is removed until you have answered the last question" sits under a five-item list that ends at the skills prompt, which is accurate for that list.

🏄 Total cleanup on a gnarly wipeout — -y used to drop in and bulldoze your whole beach, now it just paddles back and tells you what is sitting there. Ran the real binary through the original repros and it holds. Only thing left is whether this rides out as a 4.2.13 or a 5.0.0, and that is the captain’s call.

…stop on a file path

The interactive "Remove existing files and continue?" question named only the directory, while
the refusal under -y listed what was in it. The question that actually deletes files now lists
them the same way.

A project name that matched an existing file crashed with a raw ENOTDIR stack, because the
directory listing ran on a file. It now stops with a plain message and exit code 1 before
anything is read, whatever the flags say.

Refs #945
@allxsmith

Copy link
Copy Markdown
Owner Author

deep-review: verify

Every open thread has a reply: fixed, with the commit, or refuted with a reason. This settles them on 39b6232.

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://393ca2b3.bestax.pages.dev

@claude claude 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.

Deep review (verify) — 3 resolved · 0 open

# Severity Area Thread Outcome Location
1 🟡 Minor API Documented break to -y ships as a patch with no BREAKING CHANGE: footer ✅ Accepted — reasoned and deliberate, now on the record create-bestax/README.md:98
2 🔵 Advisory Robustness Target path that exists as a file crashed with a raw ENOTDIR stack ✅ Verified fixed create-bestax/src/file-system.ts:34
3 🔵 Advisory Robustness The one prompt that deletes files did not say which files ✅ Verified fixed create-bestax/src/project-creator.ts:121

What I checked

  • #1 — refutation accepted. The reply is from allxsmith (live role admin) and gives a reason rather than a bare "out of scope": the old -y emptied a folder before the other flags were even checked, which is data loss rather than a contract. That was the second of the two options the original finding offered, and the choice is now explicit in the PR body ("It ships as a patch on purpose…"), in create-bestax/README.md:97-98, and in the -y refusal itself, which names --overwrite. A reasoned deliberate choice — accepted, not re-raised.
  • #2 — fixed. existsAsNonDirectory (file-system.ts:23-29) uses fs.statSync(path, { throwIfNoEntry: false }), and checkExistingDirectory (project-creator.ts:109-112) calls it before listDirectoryEntries, so nothing reads the path and ENOTDIR can no longer be thrown. It also sits ahead of the --overwrite branch, so a file path stops even with that flag — project-creator.test.ts:261-279 passes { yes: true, overwrite: true } and asserts listDirectoryEntries is never called. The dedicated check the finding recommended was taken over the empty-listing alternative.
  • #3 — fixed. promptOverwriteDirectory(targetDir, entries) (project-creator.ts:128) threads the listing through prompts.ts:40-50 into MESSAGES.DIRECTORY_NOT_EMPTY (constants.ts:43-44), which renders through the same capped listEntries helper the -y refusal uses, so the path that deletes files now names them. project-creator.test.ts:195-213 asserts the prompt text contains (notes.txt) and that emptyDirectory is still not called at answer time; :281-294 holds the cap.
  • Test run: pnpm --filter create-bestax run test — 9 suites, 306 tests, all passing. Empirical CLI invocations were not possible in this session: the sandbox blocks filesystem writes outside the repository, so the assertions above rest on the unit tests plus the code paths rather than on a fresh dist run.

Overall: This pass settled threads and reviewed no commits. All three previously open threads are now closed — two fixed in 39b6232b and verified against the current code, one accepted on a reasoned refutation from an admin. Nothing is left open, and no new findings were raised, by design; a maintainer wanting the current code reviewed afresh should post a deep-review: fresh steer.

Residual risk: Not assessed — a verify pass settles existing threads and does not hunt for new failure modes. The two fixes verified here were checked against the tests that pin them, so the specific defects they addressed are closed.

🏄 Three threads paddled in clean — two patched up proper, and one where the maintainer called the drop right on patch-versus-major. Board is waxed, no chop left in the lineup. Good to go, dude.

@github-actions github-actions Bot added the review-converged Deep review converged: no blocking findings, all review threads resolved, checks green label Oct 8, 2026
@allxsmith allxsmith added the needs-human-review Loop converged (or contested): awaiting owner review + manual merge label Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

📸 Story screenshots at handoff — 39b6232

No Storybook stories map to this PR's changed files — nothing to screenshot. (workflow run)

@allxsmith
allxsmith merged commit 4927846 into main Oct 8, 2026
93 checks passed
@allxsmith
allxsmith deleted the fix/945-yes-overwrite branch October 8, 2026 03:07
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 5.27.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 4.2.13 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 2.25.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 1.14.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

deep-review needs-human-review Loop converged (or contested): awaiting owner review + manual merge released review-converged Deep review converged: no blocking findings, all review threads resolved, checks green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

create-bestax -y empties an existing folder without asking, before validating flags

1 participant