Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ Version 2.0.25

To be released.

### @fedify/init

- Added permanent removal warning for Linux or other UNIX-like system users
before delete the existing content in the project directory.
[[#989] by Jungmin Yoon\]

@sij411 sij411 Aug 16, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just letting you know, '\' looks like a typo

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It was written by sacho formatter, so I thought that it was a rule of formatter.
I would delete this typo if needed


[#989]: https://github.com/fedify-dev/fedify/issues/989


Version 2.0.24
--------------
Expand Down
5 changes: 5 additions & 0 deletions changes.d/init/permanent-delete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- Added permanent removal warning for Linux or other UNIX-like system users
before delete the existing content in the project directory.
[[#989] by Jungmin Yoon]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just a reminder, you should add pr link. for example, [#997], [#997]: https://github.com/fedify-dev/fedify/pull/997


[#989]: https://github.com/fedify-dev/fedify/issues/989
103 changes: 79 additions & 24 deletions packages/init/src/ask/dir.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { identity, pipe, when } from "@fxts/core";
import { input } from "@inquirer/prompts";
import { message } from "@optique/core/message";
import { type Message, message } from "@optique/core/message";
import { printError } from "@optique/run";
import toggle from "inquirer-toggle";
import { getCwd, getOsType, runSubCommand } from "../utils.ts";
import { isDirectoryEmpty, logger } from "../lib.ts";
import { getCwd, getOsType, runSubCommand } from "../utils.ts";

/**
* Fills in the project directory by prompting the user if not provided.
Expand All @@ -26,12 +26,14 @@ const fillDir: <T extends { dir?: string }>(

export default fillDir;

type DirActionMode = "trash" | "permanent";

const askDir = (cwd: string) =>
input({ message: "Project directory:", default: cwd });

const askIfNonEmpty = async (dir: string) => {
if (await isDirectoryEmpty(dir)) return true;
if (await askNonEmpty(dir)) return await moveDirToTrash(dir);
if (await askNonEmpty(dir)) return await moveOrDeleteDir(dir);
return false;
};

Expand All @@ -42,47 +44,91 @@ Do you want to use it anyway?`,
default: false,
});

const moveDirToTrash = (dir: string) =>
pipe(dir, askMoveToTrash, when(identity, moveToTrash(dir)));
const moveOrDeleteDir = (dir: string) =>
pipe(
dir,
buildMoveOrDelete,
(action) =>
pipe(
action.confirm,
when(identity, runAction(action.command, action.error)),
),
);

const buildMoveOrDelete = (dir: string) =>
pipe(
getOsType(),
detectSupportedAction,
(fn) => {
return {
confirm: fn.confirmAction(dir),
command: fn.actionPlan.command(dir),
error: errorMessages[fn.actionPlan.mode](dir),
};
},
);

const detectSupportedAction = (os: NodeJS.Platform) => {
const actionPlan =
trashOrDeleteCommands[os as keyof typeof trashOrDeleteCommands] ??
trashOrDeleteCommands.linux;

return {
confirmAction: (actionPlan.mode === "trash")
? moveToTrashConfirm
: deletePermanentlyConfirm,
actionPlan,
};
};

const askMoveToTrash = (dir: string) =>
const moveToTrashConfirm = (dir: string) =>
toggle.default({
message: `Do you want to move the contents of "${dir}" to the trash?
If you choose "No", you should choose another directory.`,
default: false,
});

const moveToTrash = (dir: string) => () =>
const deletePermanentlyConfirm = (dir: string) =>
toggle.default({
message: `Do you really want to delete all contents of "${dir}" PERMANENTLY?
If you choose "No", you should choose another directory.`,
default: false,
});

const runAction = (command: string[], error: Message) => () =>
pipe(
getOsType(),
getTrashCommand,
(fn) => fn(dir),
command,
(cmd) => runSubCommand(cmd, { stdio: "ignore" }),
() => true,
).catch((e) => {
logger.error(e);
printError(message`Failed to move ${dir} to trash.
Please move it manually.`);
printError(error);
return false;
});

const getTrashCommand = (os: NodeJS.Platform) =>
trashCommands[os as keyof typeof trashCommands] ?? trashCommands.linux;

const trashCommands: Record<
const trashOrDeleteCommands: Record<
Extract<NodeJS.Platform, "darwin" | "win32" | "linux">,
(dir: string) => string[]
{ mode: DirActionMode; command: (dir: string) => string[] }
> = {
// mac
darwin: (dir: string) => ["trash", dir],
darwin: {
mode: "trash",
command: (dir: string) => ["trash", dir],
},
// windows
win32: (dir: string) => [
"powershell",
"-Command",
getPowershellTrashCommand(dir),
],
win32: {
mode: "trash",
command: (dir: string) => [
"powershell",
"-Command",
getPowershellTrashCommand(dir),
],
},
// other unix
linux: (dir: string) => ["rm", "-rf", dir],
linux: {
mode: "permanent",
command: (dir: string) => ["rm", "-rf", dir],
},
};

const getPowershellTrashCommand = (dir: string) =>
Expand All @@ -96,3 +142,12 @@ const getPowershellTrashCommand = (dir: string) =>
"'OnlyErrorDialogs',",
"'SendToRecycleBin')",
].join(" ");

const errorMessages: Record<DirActionMode, (dir: string) => Message> = {
"trash": (dir: string) =>
message`Failed to move ${dir} to trash.
Please move it manually.`,
"permanent": (dir: string) =>
message`Failed to delete ${dir} permanently.
Please remove it manually.`,
};