fix(create-tldraw): create subdirectory from project name#8161
fix(create-tldraw): create subdirectory from project name#8161
Conversation
…installing into cwd Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
5 Skipped Deployments
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed:
findAvailableDircrashes when candidate path is a file- Updated
isDirEmptyto treat existing non-directory paths as unavailable by checkingstatSync(path).isDirectory()before reading entries, preventing ENOTDIR crashes for file candidates.
- Updated
Or push these changes by commenting:
@cursor push ffa6159acb
Preview (ffa6159acb)
diff --git a/packages/create-tldraw/src/utils.ts b/packages/create-tldraw/src/utils.ts
--- a/packages/create-tldraw/src/utils.ts
+++ b/packages/create-tldraw/src/utils.ts
@@ -1,5 +1,5 @@
import { isCancel, outro } from '@clack/prompts'
-import { existsSync, readdirSync } from 'node:fs'
+import { existsSync, readdirSync, statSync } from 'node:fs'
import { basename, resolve } from 'node:path'
export function nicelog(...args: unknown[]) {
@@ -12,6 +12,10 @@
return true
}
+ if (!statSync(path).isDirectory()) {
+ return false
+ }
+
const files = readdirSync(path)
return files.length === 0 || (files.length === 1 && files[0] === '.git')
}|
I'm down with this change! @steveruizok would you like to weigh in? |
mimecuvalo
left a comment
There was a problem hiding this comment.
LGTM pre-approved module the one cursor comment
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Silent directory rename changes package name without notification
- The CLI now preserves the user-selected app name for
package.jsonand logs when it switches to a suffixed directory because the requested one already exists.
- The CLI now preserves the user-selected app name for
- ✅ Fixed: Unbounded loop in
findAvailableDirlacks safety limitfindAvailableDirnow enforces a 1000-attempt cap and throws a clear error if no available directory is found within that limit.
Or push these changes by commenting:
@cursor push b903f05838
Preview (b903f05838)
diff --git a/packages/create-tldraw/src/main.ts b/packages/create-tldraw/src/main.ts
--- a/packages/create-tldraw/src/main.ts
+++ b/packages/create-tldraw/src/main.ts
@@ -43,11 +43,20 @@
const template = await templatePicker(args.template, args['no-telemetry'])
const name = await namePicker(maybeTargetDir)
- const targetDir = findAvailableDir(maybeTargetDir ?? resolve(process.cwd(), name))
+ const requestedTargetDir = maybeTargetDir ?? resolve(process.cwd(), name)
+ const targetDir = findAvailableDir(requestedTargetDir)
+ if (targetDir !== requestedTargetDir) {
+ nicelog(
+ `${relative(process.cwd(), requestedTargetDir)} already exists, using ${relative(
+ process.cwd(),
+ targetDir
+ )} instead.`
+ )
+ }
mkdirSync(targetDir, { recursive: true })
await downloadTemplate(template, targetDir)
- await renameTemplate(pathToName(targetDir), targetDir)
+ await renameTemplate(name, targetDir)
const manager = getPackageManager()
const doneMessage = ['Done! Now run:', '']
@@ -142,10 +151,15 @@
function findAvailableDir(targetDir: string): string {
if (isDirEmpty(targetDir)) return targetDir
- for (let i = 1; ; i++) {
+ const maxAttempts = 1000
+ for (let i = 1; i <= maxAttempts; i++) {
const candidate = `${targetDir}-${i}`
if (isDirEmpty(candidate)) return candidate
}
+
+ throw new Error(
+ `Could not find an available directory for ${targetDir} after ${maxAttempts} attempts.`
+ )
}
async function downloadTemplate(template: Template, targetDir: string) {This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
| if (isDirEmpty(targetDir)) { | ||
| mkdirSync(targetDir, { recursive: true }) | ||
| return | ||
| } |
There was a problem hiding this comment.
Unbounded loop in findAvailableDir lacks safety limit
Low Severity
The for (let i = 1; ; i++) loop in findAvailableDir has no upper bound. While it will practically always terminate quickly, there's no safety valve — if many similarly-named directories exist (e.g., from scripted usage), the loop spins indefinitely with no timeout or maximum attempts. Adding a reasonable cap (and a clear error message if exceeded) would make this more robust.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| await ensureDirectoryEmpty(targetDir, args.overwrite) | ||
| const requestedDir = maybeTargetDir ?? resolve(process.cwd(), name) | ||
| const targetDir = findAvailableDir(requestedDir) | ||
| mkdirSync(targetDir, { recursive: true }) |
There was a problem hiding this comment.
Silent directory rename without notifying the user
Medium Severity
When the requested directory already exists, findAvailableDir silently changes the target to a suffixed name (e.g., my-app-1) without ever telling the user. The only hint is the cd path in the outro message, which is easy to miss. A user running npm create tldraw my-app would naturally try cd my-app afterward and not find their project. There needs to be an explicit message when the directory is changed from what the user requested.



Closes #8148
Summary
npm create tldrawnow always creates a subdirectory from the project name instead of installing into the current directory-1,-2, etc. — never overwrites--overwriteflag and "directory is not empty" prompt, since they're no longer neededThis one change fixes both bugs from the issue:
Test plan
cli.cjsfrom a non-empty directory, type a name → verify files go into a new subdirectory<name>-1instead of overwritingChange type
bugfiximprovementfeatureapiother🤖 Generated with Claude Code
Note
Medium Risk
Behavioral change to CLI output location and directory selection could affect existing user workflows/scripts, but it’s limited to project scaffolding and reduces risk of accidental overwrites.
Overview
create-tldrawno longer scaffolds into the current directory or overwrites an existing target. It now derives a project directory from the provided/entered app name, and if that directory already exists it automatically picks the next available-1,-2, etc. suffix.This removes the
--overwriteflag and the interactive “directory not empty” prompt/cleanup path, replaces it withfindAvailableDir, and hardensisDirEmptyto treat non-directories as non-empty (with new unit tests covering file paths and.git-only dirs).Written by Cursor Bugbot for commit 846d76e. This will update automatically on new commits. Configure here.