Skip to content

Commit 518f947

Browse files
committed
feat(cli): warn before creating in non-empty target dirs
1 parent 164522e commit 518f947

2 files changed

Lines changed: 35 additions & 20 deletions

File tree

packages/cli/src/cli.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fs from 'node:fs'
22
import { resolve } from 'node:path'
33
import { Command, InvalidArgumentError } from 'commander'
4-
import { intro, log } from '@clack/prompts'
4+
import { cancel, confirm, intro, isCancel, log } from '@clack/prompts'
55
import chalk from 'chalk'
66
import semver from 'semver'
77

@@ -70,6 +70,37 @@ export function cli({
7070

7171
const program = new Command()
7272

73+
async function confirmTargetDirectorySafety(
74+
targetDir: string,
75+
forced?: boolean,
76+
) {
77+
if (forced) {
78+
return
79+
}
80+
81+
if (!fs.existsSync(targetDir)) {
82+
return
83+
}
84+
85+
if (!fs.statSync(targetDir).isDirectory()) {
86+
throw new Error(`Target path exists and is not a directory: ${targetDir}`)
87+
}
88+
89+
if (fs.readdirSync(targetDir).length === 0) {
90+
return
91+
}
92+
93+
const shouldContinue = await confirm({
94+
message: `Target directory "${targetDir}" already exists and is not empty. Continue anyway?`,
95+
initialValue: false,
96+
})
97+
98+
if (isCancel(shouldContinue) || !shouldContinue) {
99+
cancel('Operation cancelled.')
100+
process.exit(0)
101+
}
102+
}
103+
73104
const availableFrameworks = getFrameworks().map((f) => f.name)
74105

75106
const toolchains = new Set<string>()
@@ -251,6 +282,7 @@ export function cli({
251282
console.log(chalk.gray('├─') + ' ' + chalk.yellow('⟳') + ' installing packages...')
252283
}
253284
const silentEnvironment = createUIEnvironment(appName, true)
285+
await confirmTargetDirectorySafety(normalizedOpts.targetDir, options.force)
254286
await createApp(silentEnvironment, normalizedOpts)
255287
console.log(chalk.gray('└─') + ' ' + chalk.green('✓') + ` app created`)
256288

@@ -342,6 +374,7 @@ export function cli({
342374
finalOptions.targetDir = resolve(process.cwd(), finalOptions.projectName)
343375
}
344376

377+
await confirmTargetDirectorySafety(finalOptions.targetDir, options.force)
345378
await createApp(environment, finalOptions)
346379
} catch (error) {
347380
log.error(

packages/cli/src/options.ts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import fs from 'node:fs'
2-
import { cancel, confirm, intro, isCancel } from '@clack/prompts'
1+
import { intro } from '@clack/prompts'
32

43
import {
54
finalizeAddOns,
@@ -59,23 +58,6 @@ export async function promptForCreateOptions(
5958
options.projectName = await getProjectName()
6059
}
6160

62-
// Check if target directory is empty
63-
if (
64-
!cliOptions.force &&
65-
fs.existsSync(options.projectName) &&
66-
fs.readdirSync(options.projectName).length > 0
67-
) {
68-
const shouldContinue = await confirm({
69-
message: `Target directory ${options.projectName} is not empty. Do you want to continue?`,
70-
initialValue: true,
71-
})
72-
73-
if (isCancel(shouldContinue) || !shouldContinue) {
74-
cancel('Operation cancelled.')
75-
process.exit(0)
76-
}
77-
}
78-
7961
// Mode is always file-router (TanStack Start)
8062
options.mode = 'file-router'
8163

0 commit comments

Comments
 (0)