From 0b3411626c18371eca18957535c4e4a24b79cde9 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Mon, 25 May 2026 21:17:42 +0200 Subject: [PATCH] feat(utils): add uniqueFilename helper with -2 collision suffix --- src/utils/uniqueFilename.spec.ts | 71 ++++++++++++++++++++++++++++++++ src/utils/uniqueFilename.ts | 21 ++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/utils/uniqueFilename.spec.ts create mode 100644 src/utils/uniqueFilename.ts diff --git a/src/utils/uniqueFilename.spec.ts b/src/utils/uniqueFilename.spec.ts new file mode 100644 index 0000000..cd0c5e2 --- /dev/null +++ b/src/utils/uniqueFilename.spec.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from "vitest" + +import { uniqueFilename } from "./uniqueFilename" + +describe("uniqueFilename", () => { + it("returns basename.ext when nothing collides", () => { + expect( + uniqueFilename({ + basename: "myfile", + extension: ".png", + existingPaths: ["docs/notes/myfile.md"], + directory: "docs/notes" + }) + ).toEqual("myfile.png") + }) + + it("appends -2 on first collision (skips -1)", () => { + expect( + uniqueFilename({ + basename: "myfile", + extension: ".png", + existingPaths: ["docs/notes/myfile.png"], + directory: "docs/notes" + }) + ).toEqual("myfile-2.png") + }) + + it("appends -3 when both base and -2 exist", () => { + expect( + uniqueFilename({ + basename: "myfile", + extension: ".png", + existingPaths: ["docs/notes/myfile.png", "docs/notes/myfile-2.png"], + directory: "docs/notes" + }) + ).toEqual("myfile-3.png") + }) + + it("fills gaps when -3 exists but -2 does not", () => { + expect( + uniqueFilename({ + basename: "myfile", + extension: ".png", + existingPaths: ["docs/notes/myfile.png", "docs/notes/myfile-3.png"], + directory: "docs/notes" + }) + ).toEqual("myfile-2.png") + }) + + it("handles repo-root directory", () => { + expect( + uniqueFilename({ + basename: "myfile", + extension: ".jpg", + existingPaths: ["myfile.jpg"], + directory: "" + }) + ).toEqual("myfile-2.jpg") + }) + + it("does not collide with a different extension", () => { + expect( + uniqueFilename({ + basename: "myfile", + extension: ".png", + existingPaths: ["docs/myfile.jpg"], + directory: "docs" + }) + ).toEqual("myfile.png") + }) +}) diff --git a/src/utils/uniqueFilename.ts b/src/utils/uniqueFilename.ts new file mode 100644 index 0000000..0e5fbcf --- /dev/null +++ b/src/utils/uniqueFilename.ts @@ -0,0 +1,21 @@ +export const uniqueFilename = ({ + basename, + extension, + existingPaths, + directory +}: { + basename: string + extension: string + existingPaths: string[] + directory: string +}): string => { + const prefix = directory ? `${directory}/` : "" + const candidate = (n: number) => + n === 1 ? `${basename}${extension}` : `${basename}-${n}${extension}` + const taken = new Set(existingPaths) + let n = 1 + while (taken.has(`${prefix}${candidate(n)}`)) { + n = n === 1 ? 2 : n + 1 + } + return candidate(n) +}