mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
fix(files): preserve relative symlinks when duplicating entries (#6771)
* fix(files): preserve relative symlinks when duplicating entries * test(files): verify duplicated symlink targets through daemon RPC
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
||||
import { mkdtempSync, writeFileSync, rmSync, mkdirSync, symlinkSync, readdirSync } from "fs";
|
||||
import {
|
||||
mkdtempSync,
|
||||
writeFileSync,
|
||||
rmSync,
|
||||
mkdirSync,
|
||||
symlinkSync,
|
||||
readdirSync,
|
||||
readlinkSync,
|
||||
} from "fs";
|
||||
import { tmpdir } from "os";
|
||||
import path from "path";
|
||||
import { createDaemonTestContext, type DaemonTestContext } from "../test-utils/index.js";
|
||||
@@ -387,6 +395,84 @@ describe("file explorer", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test.skipIf(process.platform === "win32")(
|
||||
"duplicating a folder keeps relative symlinks inside the copy",
|
||||
async () => {
|
||||
const cwd = tmpCwd();
|
||||
try {
|
||||
mkdirSync(path.join(cwd, "template"));
|
||||
writeFileSync(path.join(cwd, "template", "data.txt"), "original payload\n");
|
||||
symlinkSync("data.txt", path.join(cwd, "template", "alias.txt"));
|
||||
|
||||
const duplicated = await ctx.client.duplicateFileEntry({ cwd, path: "template" });
|
||||
expect(duplicated).toMatchObject({
|
||||
duplicatedPath: "template copy",
|
||||
success: true,
|
||||
error: null,
|
||||
});
|
||||
expect(readlinkSync(path.join(cwd, "template copy", "alias.txt"))).toBe("data.txt");
|
||||
|
||||
const copiedTarget = await ctx.client.readFile(cwd, "template copy/data.txt");
|
||||
const targetWrite = await ctx.client.writeFile({
|
||||
cwd,
|
||||
path: "template copy/data.txt",
|
||||
content: "copy payload\n",
|
||||
expectedModifiedAt: copiedTarget.modifiedAt,
|
||||
expectedRevision: copiedTarget.revision,
|
||||
});
|
||||
expect(targetWrite.status).toBe("written");
|
||||
const copiedAlias = await ctx.client.readFile(cwd, "template copy/alias.txt");
|
||||
expect(new TextDecoder().decode(copiedAlias.bytes)).toBe("copy payload\n");
|
||||
|
||||
const aliasWrite = await ctx.client.writeFile({
|
||||
cwd,
|
||||
path: "template copy/alias.txt",
|
||||
content: "edited through copied alias\n",
|
||||
expectedModifiedAt: copiedAlias.modifiedAt,
|
||||
expectedRevision: copiedAlias.revision,
|
||||
});
|
||||
expect(aliasWrite.status).toBe("written");
|
||||
const original = await ctx.client.readFile(cwd, "template/data.txt");
|
||||
expect(new TextDecoder().decode(original.bytes)).toBe("original payload\n");
|
||||
const editedCopy = await ctx.client.readFile(cwd, "template copy/data.txt");
|
||||
expect(new TextDecoder().decode(editedCopy.bytes)).toBe("edited through copied alias\n");
|
||||
|
||||
const deleted = await ctx.client.deleteFileEntry({ cwd, path: "template" });
|
||||
expect(deleted).toMatchObject({ success: true, error: null });
|
||||
const survivingAlias = await ctx.client.readFile(cwd, "template copy/alias.txt");
|
||||
expect(new TextDecoder().decode(survivingAlias.bytes)).toBe(
|
||||
"edited through copied alias\n",
|
||||
);
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test.skipIf(process.platform === "win32")(
|
||||
"duplicating a file symlink preserves an explicit absolute target",
|
||||
async () => {
|
||||
const cwd = tmpCwd();
|
||||
const targetPath = path.join(cwd, "data.txt");
|
||||
try {
|
||||
writeFileSync(targetPath, "absolute target payload\n");
|
||||
symlinkSync(targetPath, path.join(cwd, "alias.txt"));
|
||||
|
||||
const duplicated = await ctx.client.duplicateFileEntry({ cwd, path: "alias.txt" });
|
||||
expect(duplicated).toMatchObject({
|
||||
duplicatedPath: "alias copy.txt",
|
||||
success: true,
|
||||
error: null,
|
||||
});
|
||||
expect(readlinkSync(path.join(cwd, "alias copy.txt"))).toBe(targetPath);
|
||||
const copy = await ctx.client.readFile(cwd, "alias copy.txt");
|
||||
expect(new TextDecoder().decode(copy.bytes)).toBe("absolute target payload\n");
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test("lists directory contents", async () => {
|
||||
const cwd = tmpCwd();
|
||||
|
||||
|
||||
@@ -669,6 +669,7 @@ export async function duplicateExplorerEntry({
|
||||
errorOnExist: true,
|
||||
force: false,
|
||||
preserveTimestamps: true,
|
||||
verbatimSymlinks: true,
|
||||
});
|
||||
return { status: "ok", path: normalizeRelativePath({ root, targetPath }) };
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user