feat: Add Advanced Batch Rename command with rule-based engine#25501
feat: Add Advanced Batch Rename command with rule-based engine#25501raycastbot merged 11 commits intoraycast:mainfrom
Conversation
|
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 commandsBRANCH="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 devWe're currently experiencing a high volume of incoming requests. As a result, the initial review may take up to 10-15 business days. |
Greptile SummaryThis PR adds an "Advanced Batch Rename" command to the Issues found:
Address these three issues before merging to ensure accurate user feedback and cleaner dependencies. Confidence Score: 3/5
Last reviewed commit: 18aae53 |
| if (fs.existsSync(newPath)) { | ||
| // Conflict! Skip for now or handle | ||
| console.error(`Skipping ${file.name} -> ${file.newName} due to conflict`); | ||
| continue; | ||
| } |
There was a problem hiding this comment.
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.:
| 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; | |
| } |
extensions/renaming/src/lib/rules.ts
Outdated
| 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; | ||
| }; |
There was a problem hiding this comment.
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!
extensions/renaming/CHANGELOG.md
Outdated
| @@ -1,5 +1,14 @@ | |||
| # Rename Changelog | |||
|
|
|||
| ## {PR_MERGE_DATE} | |||
There was a problem hiding this comment.
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:
| ## {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); |
There was a problem hiding this comment.
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
|
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? |
|
Hi @ridemountainpig @0xdhrv kindly review Thanks. |
| }; | ||
|
|
||
| const applyRename = async () => { | ||
| if (await confirmAlert({ title: `Rename ${previewFiles.length} files?`, primaryAction: { title: "Rename" } })) { |
There was a problem hiding this comment.
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:
| 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"; |
There was a problem hiding this comment.
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.
| import { v4 as uuidv4 } from "uuid"; | |
| import { randomUUID } from "crypto"; |
Then replace uuidv4() with randomUUID() on line 92.
extensions/renaming/package.json
Outdated
| "@raycast/api": "^1.65.1", | ||
| "@raycast/utils": "^1.12.5" | ||
| "@raycast/utils": "^1.12.5", | ||
| "uuid": "^13.0.0" |
There was a problem hiding this comment.
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.
| "uuid": "^13.0.0" | |
| Remove lines 52 and 58 from this file: | |
| - "uuid": "^13.0.0" | |
| - "@types/uuid": "^10.0.0" |
extensions/renaming/src/lib/rules.ts
Outdated
| case "titlecase": | ||
| currentName = currentName.replace( | ||
| /\w\S*/g, | ||
| (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(), |
There was a problem hiding this comment.
String.prototype.substr is deprecated
substr was removed from the ECMAScript standard and is deprecated in modern JavaScript runtimes. Use substring instead.
| (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(), | |
| (txt) => txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase(), |
ridemountainpig
left a comment
There was a problem hiding this comment.
Looks good to me.
0xdhrv
left a comment
There was a problem hiding this comment.
Looks good to me, approved ✅
|
Published to the Raycast Store: |
|
🎉 🎉 🎉 We've rewarded your Raycast account with some credits. You will soon be able to exchange them for some swag. |
- 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)
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:
Screencast
Checklist
npm run buildand tested this distribution build in Raycastassetsfolder are used by the extension itselfREADMEare located outside the metadata folder if they were not generated with our metadata tool