Files
create-better-t-stack/apps/cli/src/utils/project-directory.ts
2025-08-12 07:40:19 +05:30

116 lines
3.3 KiB
TypeScript

import path from "node:path";
import { isCancel, log, select, spinner } from "@clack/prompts";
import fs from "fs-extra";
import pc from "picocolors";
import { getProjectName } from "../prompts/project-name";
import { exitCancelled, handleError } from "./errors";
export async function handleDirectoryConflict(
currentPathInput: string,
silent = false,
): Promise<{
finalPathInput: string;
shouldClearDirectory: boolean;
}> {
while (true) {
const resolvedPath = path.resolve(process.cwd(), currentPathInput);
const dirExists = fs.pathExistsSync(resolvedPath);
const dirIsNotEmpty = dirExists && fs.readdirSync(resolvedPath).length > 0;
if (!dirIsNotEmpty) {
return { finalPathInput: currentPathInput, shouldClearDirectory: false };
}
if (silent) {
throw new Error(
`Directory "${currentPathInput}" already exists and is not empty. In silent mode, please provide a different project name or clear the directory manually.`,
);
}
log.warn(
`Directory "${pc.yellow(
currentPathInput,
)}" already exists and is not empty.`,
);
const action = await select<"overwrite" | "merge" | "rename" | "cancel">({
message: "What would you like to do?",
options: [
{
value: "overwrite",
label: "Overwrite",
hint: "Empty the directory and create the project",
},
{
value: "merge",
label: "Merge",
hint: "Create project files inside, potentially overwriting conflicts",
},
{
value: "rename",
label: "Choose a different name/path",
hint: "Keep the existing directory and create a new one",
},
{ value: "cancel", label: "Cancel", hint: "Abort the process" },
],
initialValue: "rename",
});
if (isCancel(action)) return exitCancelled("Operation cancelled.");
switch (action) {
case "overwrite":
return { finalPathInput: currentPathInput, shouldClearDirectory: true };
case "merge":
log.info(
`Proceeding into existing directory "${pc.yellow(
currentPathInput,
)}". Files may be overwritten.`,
);
return {
finalPathInput: currentPathInput,
shouldClearDirectory: false,
};
case "rename": {
log.info("Please choose a different project name or path.");
const newPathInput = await getProjectName(undefined);
return await handleDirectoryConflict(newPathInput);
}
case "cancel":
return exitCancelled("Operation cancelled.");
}
}
}
export async function setupProjectDirectory(
finalPathInput: string,
shouldClearDirectory: boolean,
): Promise<{ finalResolvedPath: string; finalBaseName: string }> {
let finalResolvedPath: string;
let finalBaseName: string;
if (finalPathInput === ".") {
finalResolvedPath = process.cwd();
finalBaseName = path.basename(finalResolvedPath);
} else {
finalResolvedPath = path.resolve(process.cwd(), finalPathInput);
finalBaseName = path.basename(finalResolvedPath);
}
if (shouldClearDirectory) {
const s = spinner();
s.start(`Clearing directory "${finalResolvedPath}"...`);
try {
await fs.emptyDir(finalResolvedPath);
s.stop(`Directory "${finalResolvedPath}" cleared.`);
} catch (error) {
s.stop(pc.red(`Failed to clear directory "${finalResolvedPath}".`));
handleError(error);
}
} else {
await fs.ensureDir(finalResolvedPath);
}
return { finalResolvedPath, finalBaseName };
}