Skip to content

feat: Add Advanced Batch Rename command with rule-based engine#25501

Merged
raycastbot merged 11 commits intoraycast:mainfrom
SandeepBaskaran:feat/advanced-renaming
Mar 5, 2026
Merged

feat: Add Advanced Batch Rename command with rule-based engine#25501
raycastbot merged 11 commits intoraycast:mainfrom
SandeepBaskaran:feat/advanced-renaming

Conversation

@SandeepBaskaran
Copy link
Contributor

Description

This PR adds a powerful Advanced Batch Rename command to the Renaming extension, allowing users to stack multiple transformation rules and preview results in real-time before applying changes.
Key Features:

  • Rule-Based Engine: Stack multiple rules like Find & Replace (Regex support), Case Conversion, Text Insertion, and Sequential Numbering.
  • Live Preview: A dynamic "Will Rename" column shows exactly what your files will look like before you commit.
  • Conflict Detection: Smartly identifies duplicate filename collisions.
  • Extension Management: Easily bulk-change or format file extensions (lowercase, uppercase, replace, or remove).
  • Safe Execution: Files are only modified on disk after explicit confirmation.

Screencast

renaming-2

Checklist

@raycastbot raycastbot added extension fix / improvement Label for PRs with extension's fix improvements extension: renaming Issues related to the renaming extension platform: macOS labels Feb 16, 2026
@raycastbot
Copy link
Collaborator

raycastbot commented Feb 16, 2026

Thank you for your contribution! 🎉

🔔 @theodaguier @pernielsentikaer @ridemountainpig you might want to have a look.

You can use this guide to learn how to check out the Pull Request locally in order to test it.

📋 Quick checkout commands
BRANCH="feat/advanced-renaming"
FORK_URL="https://github.com/SandeepBaskaran/raycast-extensions.git"
EXTENSION_NAME="renaming"
REPO_NAME="raycast-extensions"

git clone -n --depth=1 --filter=tree:0 -b $BRANCH $FORK_URL
cd $REPO_NAME
git sparse-checkout set --no-cone "extensions/$EXTENSION_NAME"
git checkout
cd "extensions/$EXTENSION_NAME"
npm install && npm run dev

We're currently experiencing a high volume of incoming requests. As a result, the initial review may take up to 10-15 business days.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 16, 2026

Greptile Summary

This PR adds an "Advanced Batch Rename" command to the renaming extension with a rule-based engine supporting Find & Replace, Case Conversion, Text Insertion, Sequential Numbering, Extension Management, and Trim operations. The implementation includes live preview and conflict detection.

Issues found:

  1. Confirmation count mismatch (advanced-rename.tsx:48): The confirmation dialog reports total file count but the rename loop only acts on files with actual name changes, potentially misleading users.

  2. Unnecessary uuid dependency (AddRuleForm.tsx:12, package.json): crypto.randomUUID() is a Node.js built-in available in Raycast extensions; the external uuid package and its mismatched type declarations are redundant.

  3. Deprecated API usage (rules.ts:107): Uses deprecated String.prototype.substr() instead of substring().

Address these three issues before merging to ensure accurate user feedback and cleaner dependencies.

Confidence Score: 3/5

  • Safe to merge after addressing the confirmation count bug and removing the unnecessary uuid dependency.
  • The main blocker is the confirmation dialog count mismatch, which misleads users about how many files will be renamed. The uuid dependency issue and deprecated substr usage are relatively minor but should be cleaned up. Once these are addressed, the PR is ready.
  • extensions/renaming/src/advanced-rename.tsx (confirmation count), extensions/renaming/package.json and extensions/renaming/src/components/AddRuleForm.tsx (unnecessary uuid dependency), extensions/renaming/src/lib/rules.ts (deprecated substr).

Last reviewed commit: 18aae53

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

8 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +56 to +60
if (fs.existsSync(newPath)) {
// Conflict! Skip for now or handle
console.error(`Skipping ${file.name} -> ${file.newName} due to conflict`);
continue;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Silent skip on rename conflicts

When a target file already exists, the rename is silently skipped with only a console.error. The user sees Renamed X files but gets no indication that some files were skipped due to conflicts. This could lead to confusion — the user may believe all files were renamed successfully.

Consider showing a warning toast or including the skip count in the success message, e.g.:

Suggested change
if (fs.existsSync(newPath)) {
// Conflict! Skip for now or handle
console.error(`Skipping ${file.name} -> ${file.newName} due to conflict`);
continue;
}
if (fs.existsSync(newPath)) {
await showToast({ style: Toast.Style.Failure, title: "Conflict", message: `${file.newName} already exists, skipping.` });
continue;
}

Comment on lines +49 to +131
export const applyRules = (file: FileItem, rules: RenameRule[], index: number): string => {
let name = file.name; // Operate on base name usually, or full name depending on logic

for (const rule of rules) {
try {
switch (rule.type) {
case "replace": {
const options = rule.options;
if (options.find) {
const flags = options.caseSensitive ? "g" : "gi";
const search = options.isRegex
? new RegExp(options.find, flags)
: options.find.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");

if (!options.isRegex) {
if (options.caseSensitive) {
name = name.replaceAll(options.find, options.replace || "");
} else {
const esc = options.find.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
name = name.replace(new RegExp(esc, "gi"), options.replace || "");
}
} else {
name = name.replace(search, options.replace || "");
}
}
break;
}

case "case": {
const options = rule.options;
switch (options.format) {
case "lowercase":
name = name.toLowerCase();
break;
case "uppercase":
name = name.toUpperCase();
break;
case "capitalize":
name = name.charAt(0).toUpperCase() + name.slice(1);
break;
case "titlecase":
name = name.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
break;
}
break;
}

case "add": {
const options = rule.options;
if (options.text) {
if (options.position === "start") name = options.text + name;
if (options.position === "end") name = name + options.text;
// Note: position 'index' removed to match UI for now
}
break;
}

case "number": {
const options = rule.options;
const start = Number(options.start) || 1;
const step = Number(options.step) || 1;
const currentNum = start + index * step;
const numStr = currentNum.toString().padStart(options.padding || 1, "0");
const separator = options.separator || "";

if (options.position === "start") name = `${numStr}${separator}${name}`;
if (options.position === "end") name = `${name}${separator}${numStr}`;
break;
}

case "trim":
name = name.trim();
break;

case "extension":
break;
}
} catch (e) {
console.error(`Rule failed: ${rule.type}`, e);
}
}
return name;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

applyRules is unused dead code

The applyRules function is exported but never imported or called anywhere in the codebase. Only applyRulesToItem (line 134) is used by hooks.ts. This entire function is a near-duplicate of the logic in applyRulesToItem and should be removed to avoid confusion and maintenance burden.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@@ -1,5 +1,14 @@
# Rename Changelog

## {PR_MERGE_DATE}
Copy link
Contributor

Choose a reason for hiding this comment

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

Changelog entry missing version label

The existing entries in this file use a version label format like ## [Enhancement] - {PR_MERGE_DATE}. This entry is missing the bracketed label. It should follow the established pattern:

Suggested change
## {PR_MERGE_DATE}
## [New Feature] - {PR_MERGE_DATE}

Context Used: Rule from dashboard - What: Changelog entries must use {PR_MERGE_DATE} placeholder in titles, be placed at the top of th... (source)

continue;
}

fs.renameSync(oldPath, newPath);
Copy link
Contributor

Choose a reason for hiding this comment

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

Inconsistent rename method with existing commands

The existing index.tsx and replace.tsx commands in this extension use AppleScript via Finder to rename files, which integrates with Finder's undo stack and handles locked files gracefully. This new command uses fs.renameSync directly, which bypasses Finder entirely. Is there a specific reason for this divergence? Using the Finder-based approach would maintain consistency and give users undo support.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

- Switch to AppleScript/Finder for renaming to support system undo
- Implement user-facing feedback for rename conflicts and errors
- Remove redundant/dead code in rules engine
- Correct changelog version label format
@0xdhrv 0xdhrv self-assigned this Feb 27, 2026
@0xdhrv
Copy link
Contributor

0xdhrv commented Feb 27, 2026

Hey @ThanhDodeurOdoo @ridemountainpig

Do you have any thoughts on this?

Looks good to me so far, nice additions

@ridemountainpig
Copy link
Contributor

Hey @ThanhDodeurOdoo @ridemountainpig

Do you have any thoughts on this?

Looks good to me so far, nice additions

I suggest that after adding and selecting a rule, its details appear in the right-hand details area. Currently, the rule context is too long to view in full.

Also, when a preview file list item is selected, provide an option to add the rule directly. At present, after checking a preview file list item, you must return to the rule items to add a new rule.

@0xdhrv 0xdhrv marked this pull request as draft February 27, 2026 11:05
@0xdhrv
Copy link
Contributor

0xdhrv commented Feb 27, 2026

Hey @ThanhDodeurOdoo @ridemountainpig
Do you have any thoughts on this?
Looks good to me so far, nice additions

I suggest that after adding and selecting a rule, its details appear in the right-hand details area. Currently, the rule context is too long to view in full.

Also, when a preview file list item is selected, provide an option to add the rule directly. At present, after checking a preview file list item, you must return to the rule items to add a new rule.

Thanks @SandeepBaskaran can you take a look at this?

@SandeepBaskaran SandeepBaskaran marked this pull request as ready for review March 4, 2026 13:25
@SandeepBaskaran
Copy link
Contributor Author

Hi @ridemountainpig
I've taken your suggestion and updated it.

@0xdhrv kindly review

Thanks.

};

const applyRename = async () => {
if (await confirmAlert({ title: `Rename ${previewFiles.length} files?`, primaryAction: { title: "Rename" } })) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Confirmation dialog shows total file count, not files-to-be-renamed count

previewFiles.length is the total number of selected files, but the rename loop (line 54) only processes files where file.newName !== path.basename(file.originalPath). If 5 files are selected but only 2 will actually rename, the dialog misleadingly says "Rename 5 files?"

Consider filtering the count beforehand:

Suggested change
if (await confirmAlert({ title: `Rename ${previewFiles.length} files?`, primaryAction: { title: "Rename" } })) {
const filesToRename = previewFiles.filter(
(f) => f.newName && f.newName !== path.basename(f.originalPath)
);
if (filesToRename.length === 0) {
await showToast({ style: Toast.Style.Failure, title: "No files to rename", message: "Add rules that change at least one filename." });
return;
}
if (await confirmAlert({ title: `Rename ${filesToRename.length} files?`, primaryAction: { title: "Rename" } })) {

Then iterate over filesToRename instead of previewFiles.

NumberOptions,
ExtensionOptions,
} from "../lib/rules";
import { v4 as uuidv4 } from "uuid";
Copy link
Contributor

Choose a reason for hiding this comment

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

uuid is an unnecessary external dependency

crypto.randomUUID() is built-in to Node.js 14.17+ and is available in the Raycast environment (as evidenced by the use of fs module elsewhere in the code). The uuid package is redundant.

Suggested change
import { v4 as uuidv4 } from "uuid";
import { randomUUID } from "crypto";

Then replace uuidv4() with randomUUID() on line 92.

"@raycast/api": "^1.65.1",
"@raycast/utils": "^1.12.5"
"@raycast/utils": "^1.12.5",
"uuid": "^13.0.0"
Copy link
Contributor

Choose a reason for hiding this comment

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

uuid dependency should be removed

Since crypto.randomUUID() is available natively (see the comment on AddRuleForm.tsx), the uuid package and its associated dev dependency @types/uuid can both be removed. Additionally, uuid@13 ships its own bundled TypeScript declarations, making @types/uuid@10 redundant and a potential source of type mismatches.

Suggested change
"uuid": "^13.0.0"
Remove lines 52 and 58 from this file:
- "uuid": "^13.0.0"
- "@types/uuid": "^10.0.0"

case "titlecase":
currentName = currentName.replace(
/\w\S*/g,
(txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(),
Copy link
Contributor

Choose a reason for hiding this comment

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

String.prototype.substr is deprecated

substr was removed from the ECMAScript standard and is deprecated in modern JavaScript runtimes. Use substring instead.

Suggested change
(txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(),
(txt) => txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase(),

Copy link
Contributor

@ridemountainpig ridemountainpig left a comment

Choose a reason for hiding this comment

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

Looks good to me.

@raycast-extensions-bot raycast-extensions-bot bot added the Approved Approved by a user label Mar 4, 2026
Copy link
Contributor

@0xdhrv 0xdhrv left a comment

Choose a reason for hiding this comment

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

Looks good to me, approved ✅

@raycastbot raycastbot merged commit 67d2056 into raycast:main Mar 5, 2026
2 checks passed
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

Published to the Raycast Store:
https://raycast.com/theo_daguier/renaming

@raycastbot
Copy link
Collaborator

🎉 🎉 🎉

We've rewarded your Raycast account with some credits. You will soon be able to exchange them for some swag.

LlaziG added a commit to LlaziG/raycast-extensions that referenced this pull request Mar 7, 2026
- chore(hugeicons-ui):Update hugeicons-ui: bump @hugeicons/core-free-icons and @hugeicons/react
- [Skills] Display Metadata from SKILL.md frontmatter (raycast#26101)
- Update CODEOWNERs (c2c1e0d)
- Add timely extension (raycast#25085)
- Fix: copy prompt/answer for selected message (raycast#25438)
- Update CODEOWNERs (46ecffc)
- Update todoist extension (raycast#26087)
- Dia: update @raycast/utils to 2.2.3 (raycast#26091)
- Update CODEOWNERs (3503a64)
- Add typewhisper extension (raycast#25733)
- Update CODEOWNERs (5ea5ef7)
- Add number-research extension (raycast#26060)
- Update trustmrr extension (raycast#26085)
- Update CODEOWNERs (40aabef)
- Add desktoprenamer extension (raycast#24610)
- Update tailwindcss extension (raycast#26067)
- Update CODEOWNERs (7a13ffd)
- Update time-awareness extension (raycast#25765)
- Update CODEOWNERs (6c50032)
- Update github-status extension (raycast#26063)
- Update anna-s-archive extension (raycast#26076)
- Update CODEOWNERs (46b879a)
- Update shopify-developer-changelog extension (raycast#25711)
- Update CODEOWNERs (22aa04d)
- Update weather extension (raycast#26074)
- Update kagi-news extension (raycast#25679)
- Update (raycast#26073)
- Update teak-raycast extension (raycast#25995)
- otp-inbox: Add Windows support (raycast#25441)
- Update CODEOWNERs (36e7a5f)
- Add trustmrr extension (raycast#26069)
- Update CODEOWNERs (d7c540c)
- Add doubao-tts extension (raycast#25705)
- GitHub: Improve auto-merge support (raycast#25256)
- Update CODEOWNERs (9f556d5)
- Update microsoft-edge extension add new feat to search and launch workspaces (raycast#25335)
- Add RouteMesh MCP server to model-context-protocol-registry (raycast#25960)
- feat: Add Advanced Batch Rename command with rule-based engine (raycast#25501)
- Update media-converter extension (raycast#25836)
- Update scheduler extension (raycast#26059)
- Docs: Update the utils docs
- [pipe-commands] Add data formatting utilities (raycast#25824)
- Update CODEOWNERs (5b660b2)
- Add hdri-library extension (raycast#25701)
- Update CODEOWNERs (2ea8b9c)
- Update ente-auth extension (raycast#25773)
- Update CODEOWNERs (b9b9e2b)
- Add grpcui extension (raycast#25697)
- Update CODEOWNERs (1730346)
- Update openrouter model search extension (raycast#26045)
- System Monitor: Fix stale temperature readings in menubar (raycast#26025)
- Update CODEOWNERs (4e3ff41)
- Fix truncated row values in pass extension (raycast#25843)
- Update CODEOWNERs (489aede)
- Add quickreferences-raycast extension (raycast#23629)
- Update CODEOWNERs (84a0a58)
- Update cleanshotx extension (raycast#25985)
- [ccusage] Hide "Usage Limits" details when using non-OAuth authentication (raycast#26009)
- Browser Bookmarks: Add support for Perplexity Comet browser (raycast#25874)
- Add Windows support (raycast#25882)
- Update bmrks extension (raycast#25952)
- Update CODEOWNERs (2faa166)
- Update radarr extension (raycast#25953)
- Update CODEOWNERs (d8b0e95)
- Update google-chrome extension (raycast#25939)
- Add PrusaConnect links to Prusa extension (raycast#25955)
- Update CODEOWNERs (9f8c615)
- Claude Code Launcher: Fix Ghostty PATH by using interactive shell (raycast#25976)
- Update aave-search extension (raycast#26016)
- uuid-generator: add Pack Type Id command (raycast#25800)
- Update CODEOWNERs (39fe5d1)
- [ccusage] Fix npx path resolution for fnm installs using XDG directories (raycast#26008)
- fix(arc): prevent duplicate windows when Arc is not running (raycast#25806)
- System Monitor: Add pin-to-display for menubar stats (raycast#25821)
- Update CODEOWNERs (ec57b0b)
- Add unified Wispr Flow extension (raycast#25218)
- Update shadcn ui extension (raycast#26011)
- Update CODEOWNERs (b354d33)
- feat(gumroad): add price filter and copy actions (raycast#25703)
- Update CODEOWNERs (134d6f9)
- Add raycast-ai-custom-providers extension (raycast#25180)
- Update CODEOWNERs (4accbb2)
- Add zo-raycast extension (raycast#25464)
- Update CODEOWNERs (227732f)
- Add job-dojo extension (raycast#25677)
- Update CODEOWNERs (eace185)
- Add wallhaven extension (raycast#25656)
- Update existing somafm extension: launch flow, refresh toasts, menu fallback (raycast#25187)
- Update CODEOWNERs (d0f014f)
- Add email-finder extension (raycast#24847)
- Update cut-out extension (raycast#25990)
- Update CODEOWNERs (1ef7a10)
- Add cut-out extension (raycast#25663)
- [Pokedex] Added support for Scarlet & Violet–style sprite artwork (raycast#25986)
- Discogs extension new functions (raycast#25686)
- Update nhk-program-search extension (raycast#25967)
- Update kimi extension (raycast#25962)
- Update CODEOWNERs (de246c1)
- Update shiori-sh extension (raycast#25944)
- fix(browser-bookmarks): fix slow initial load and open-in-browser reliability (raycast#25979)
- Update CODEOWNERs (0ad09cd)
- Add spacer extension (raycast#25652)
- [zotero] Fix Zotero 7+ / Better BibTeX compatibility (raycast#25634)
- Docs: update for the new API release
- added ARM64 sdk support (raycast#25966)
- Update CODEOWNERs (3051c01)
- Add Bird extension (raycast#25481)
- Update CODEOWNERs (7c4f8af)
- Add Lock Time extension (raycast#25255)
- Update CODEOWNERs (cdc0ceb)
- Add paste-safely extension (raycast#25951)
- Update CODEOWNERs (bd032c8)
- Add polars-documentation-search extension (raycast#25589)
- Update CODEOWNERs (564b0f2)
- Add DevContainer Features extension (raycast#25603)
- Update CODEOWNERs (2cdb8f6)
- Update gift-stardew-valley extension (raycast#25552)
- Update CODEOWNERs (f728891)
- Update Inkdrop extension (raycast#25529)
- Sourcegraph: Setup improvements (raycast#25950)
- [Skills] Add support for updating skills (raycast#25887)
- Update CODEOWNERs (cb956f6)
- Add search repositories feature for Github for Enterprise (raycast#25661)
- Fix/trakt manager user agent v2 (raycast#25825)
- Update `CricketCast` extension - add menu bar for scores (raycast#25942)
- Add Windows platform support to Goodreads extension (raycast#25936)
- idonthavespotify: Add Qobuz, Bandcamp, Pandora support & fix crash on unknown adapters (raycast#25937)
- Update singularityapp extension (raycast#25943)
- Update raycast-surge extension (raycast#25883)
- Update awork extension (raycast#25844)
- Update extend-display extension (raycast#25894)
- Update git-worktrees extension (raycast#25898)
- [Image Modification] Fix QSpace / QSpace Pro selection detection (raycast#25923)
- Update zeabur extension (raycast#25924)
- Update vietnamese-calendar extension (raycast#25917)
- [AzTU LMS] Fix Color & Add New Image (raycast#25912)
- Update CODEOWNERs (c2aba2b)
- Add Hop extension (raycast#25162)
- [Music Assistant Controls]:  Big update with many features (raycast#25860)
- [MXroute] set mail hosting status + open webmail link (raycast#25895)
- Update kitty extension (raycast#25856)
- Update CODEOWNERs (b73dbee)
- Addeed SDK implementation (raycast#25820)
- Update CODEOWNERs (66857dc)
- Add notilight-controller extension (raycast#25424)
- Update raycast-store-updates extension (raycast#25865)
- Update reader-mode extension (raycast#25872)
- Update CODEOWNERs (f8eeb0d)
- Update battery-optimizer extension (raycast#25509)
- fix: show window icons on first load in window-walker extension (raycast#25871)
- Update CODEOWNERs (7e705b7)
- Update t3 chat extension (raycast#25803)
- Update CODEOWNERs (19f337b)
- Update modify-hash extension (raycast#25816)
- Update CODEOWNERs (64e21d0)
- Add `ZeroSSL` extension - list certificates, view + validate csr (raycast#25861)
- [Cron Manager] Fix tasks disappearing from UI & permission handling (raycast#25845)
- Update CODEOWNERs (abe1d59)
- Add markdown-converter extension (raycast#24129)
- Update betaseries extension (raycast#25842)
- [Skills] Inline detail Panel (raycast#25658)
- Update CODEOWNERs (f1bac6d)
- Removed two extensions (raycast#25851)
- Update CODEOWNERs (53db7b3)
- Add shiori-sh extension (raycast#25757)
- Docs: update for the new API release
- feat(everything-search): allow custom cli arguments (raycast#24607)
- Update CODEOWNERs (93ff0be)
- Delete extensions/proton-pass-client directory (raycast#25841)
- update (raycast#25840)
- Update CODEOWNERs (d85419c)
- Add kaneo-for-raycast extension (raycast#25461)
- [Apple Reminders] Prevent accidental recurring reminders from AI (raycast#25746)
- [Apple Notes] Fix AI tool note ID mismatch, timeout, and search filtering (raycast#25720)
- fix: show window icons on first load in window-walker extension (raycast#25818)
- [Namecheap] fix error when no domain dns hosts (raycast#25827)
- Update youversion-suggest extension (raycast#25797)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Approved Approved by a user extension fix / improvement Label for PRs with extension's fix improvements extension: renaming Issues related to the renaming extension platform: macOS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants