Files
Recordly/electron/ipc/project/renameLibraryProject.ts

48 lines
1.5 KiB
TypeScript

import fs from "node:fs/promises";
import path from "node:path";
export async function renameLibraryProject(
source: string,
name: string,
allowedPaths: string[],
sidecars: (path: string) => string[],
) {
if (!allowedPaths.includes(source)) throw new Error("Project is not in the library");
const clean = name.trim();
if (
!clean ||
clean === "." ||
clean === ".." ||
/[<>:"/\\|?*]/.test(clean) ||
[...clean].some((char) => char.charCodeAt(0) < 32) ||
/[. ]$/.test(clean)
)
throw new Error("Choose a valid project name");
const target = path.join(path.dirname(source), `${clean}${path.extname(source)}`);
if (target === source) return target;
// An exclusive link prevents a rename from overwriting another project.
await fs.link(source, target);
const copied: string[] = [];
try {
const previous = sidecars(source),
next = sidecars(target);
for (let i = 0; i < previous.length; i++) {
try {
await fs.copyFile(previous[i], next[i], fs.constants.COPYFILE_EXCL);
copied.push(next[i]);
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
}
}
await fs.unlink(source);
} catch (error) {
await Promise.all([target, ...copied].map((file) => fs.rm(file, { force: true })));
throw error;
}
// Auxiliary files can be cleaned up independently once the project has moved.
await Promise.all(
sidecars(source).map((file) => fs.rm(file, { force: true }).catch(() => undefined)),
);
return target;
}