mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-15 13:58:20 +00:00
fix(studio-server): HTML takes a folder-relative path only where its file exists, as the runtime reads it
This commit is contained in:
@@ -338,12 +338,61 @@ describe("renaming a folder over the route", () => {
|
||||
join(project, "a", "style.css"),
|
||||
'url(b/../logo.png) url(b/sub/../../logo.png) url(b\\.\\..\\logo.png) url("b/../in/logo.png") url("b/sub dir/../../logo.png") url("b/s,(;)/../../logo.png") url(b/image.png)',
|
||||
);
|
||||
writeFileSync(join(project, "index.html"), '<img src="a/b/../logo.png">');
|
||||
|
||||
expect((await renameIn(project, "a/b", "b/c")).status).toBe(200);
|
||||
expect(readFileSync(join(project, "a", "style.css"), "utf8")).toBe(
|
||||
'url(b/../logo.png) url(b/sub/../../logo.png) url(b\\.\\..\\logo.png) url("b/../in/logo.png") url("b/sub dir/../../logo.png") url("b/s,(;)/../../logo.png") url(../b/c/image.png)',
|
||||
);
|
||||
expect(readFileSync(join(project, "index.html"), "utf8")).toBe('<img src="a/b/../logo.png">');
|
||||
});
|
||||
|
||||
it("in HTML takes the folder reading only where its file exists, else leaves the root path to the root pass", async () => {
|
||||
const project = mkdtempSync(join(tmpdir(), "hf-rename-refs-"));
|
||||
dirs.push(project);
|
||||
mkdirSync(join(project, "assets"));
|
||||
mkdirSync(join(project, "compositions", "assets"), { recursive: true });
|
||||
writeFileSync(join(project, "assets", "logo.png"), "x");
|
||||
writeFileSync(join(project, "compositions", "assets", "bg.png"), "x");
|
||||
writeFileSync(
|
||||
join(project, "compositions", "hero.html"),
|
||||
'<img src="assets/logo.png"><img src="assets/bg.png">',
|
||||
);
|
||||
|
||||
expect((await renameIn(project, "compositions/assets", "compositions/media")).status).toBe(200);
|
||||
expect(readFileSync(join(project, "compositions", "hero.html"), "utf8")).toBe(
|
||||
'<img src="assets/logo.png"><img src="media/bg.png">',
|
||||
);
|
||||
});
|
||||
|
||||
it("in HTML gives a path the runtime reads from the root to the root pass, where both readings match", async () => {
|
||||
const project = mkdtempSync(join(tmpdir(), "hf-rename-refs-"));
|
||||
dirs.push(project);
|
||||
mkdirSync(join(project, "scenes", "scenes"), { recursive: true });
|
||||
writeFileSync(join(project, "scenes", "scenes", "clip.mp4"), "x");
|
||||
writeFileSync(join(project, "scenes", "intro.html"), '<video src="scenes/scenes/clip.mp4">');
|
||||
|
||||
expect((await renameIn(project, "scenes/scenes", "scenes/shots")).status).toBe(200);
|
||||
expect(readFileSync(join(project, "scenes", "intro.html"), "utf8")).toBe(
|
||||
'<video src="scenes/shots/clip.mp4">',
|
||||
);
|
||||
});
|
||||
|
||||
it("edits each reference once when the new path ends in the old one, and keeps a longer folder's path", async () => {
|
||||
const project = mkdtempSync(join(tmpdir(), "hf-rename-refs-"));
|
||||
dirs.push(project);
|
||||
mkdirSync(join(project, "a", "b"), { recursive: true });
|
||||
mkdirSync(join(project, "assets", "clips"), { recursive: true });
|
||||
mkdirSync(join(project, "assets", "old clips"), { recursive: true });
|
||||
writeFileSync(join(project, "a", "b", "x.png"), "x");
|
||||
writeFileSync(join(project, "assets", "clips", "a.png"), "x");
|
||||
writeFileSync(join(project, "assets", "old clips", "a.png"), "x");
|
||||
writeFileSync(join(project, "a", "s.css"), "url(b/x.png)");
|
||||
writeFileSync(join(project, "assets", "s.css"), 'url("old clips/a.png") url(clips/a.png)');
|
||||
|
||||
expect((await renameIn(project, "a/b", "a/a/b")).status).toBe(200);
|
||||
expect(readFileSync(join(project, "a", "s.css"), "utf8")).toBe("url(a/b/x.png)");
|
||||
expect((await renameIn(project, "assets/clips", "assets/takes")).status).toBe(200);
|
||||
expect(readFileSync(join(project, "assets", "s.css"), "utf8")).toBe(
|
||||
'url("old clips/a.png") url(takes/a.png)',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -703,9 +703,9 @@ function referenceEdits(
|
||||
newPath: string,
|
||||
isDirectory: boolean,
|
||||
existing: readonly string[],
|
||||
bareOrDotLead = false,
|
||||
beside: { targets: ReadonlySet<string> | null } | null = null,
|
||||
): (text: string) => Edit[] {
|
||||
const pattern = referencePattern(oldPath, isDirectory, bareOrDotLead);
|
||||
const pattern = referencePattern(oldPath, isDirectory, beside !== null);
|
||||
const around = new Map<string, Map<number, Set<string>>>();
|
||||
const afterLengths = new Set<number>();
|
||||
let window = 0;
|
||||
@@ -738,9 +738,13 @@ function referenceEdits(
|
||||
);
|
||||
});
|
||||
const end = offset + match.length;
|
||||
const rest = /^[^"'`<>\r\n]*/.exec(text.slice(end, end + MAX_REST))![0];
|
||||
const climbsOut = isDirectory && leavesFolder(normalized(rest));
|
||||
return inLonger || climbsOut ? [] : [{ at, end, text: newPath }];
|
||||
if (inLonger) return [];
|
||||
if (beside) {
|
||||
const rest = normalized(/^[^"'`<>\r\n]*/.exec(text.slice(end, end + MAX_REST))![0]);
|
||||
if (isDirectory && leavesFolder(rest)) return [];
|
||||
if (beside.targets && !reaches(beside.targets, `${newPath}${rest}`)) return [];
|
||||
}
|
||||
return [{ at, end, text: newPath }];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -754,6 +758,14 @@ export function referenceRewriter(
|
||||
return (text) => applyEdits(text, edits(text));
|
||||
}
|
||||
|
||||
// HTML reads a bare path beside itself only where that file exists, else from the project root (rewriteAssetPath).
|
||||
function reaches(targets: ReadonlySet<string>, path: string): boolean {
|
||||
for (let i = 0; i <= path.length; i++)
|
||||
if ((i === path.length || /[\s?#),;]/.test(path[i]!)) && targets.has(path.slice(0, i)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function leavesFolder(rest: string): boolean {
|
||||
let depth = 0;
|
||||
for (const segment of rest.split("/")) {
|
||||
@@ -810,22 +822,17 @@ function updateReferences(
|
||||
|
||||
const existing = projectPaths(projectDir);
|
||||
const rootEdits = referenceEdits(oldPath, newPath, isDirectory, existing);
|
||||
const parentFolderEdits = new Map<string, (text: string) => Edit[]>();
|
||||
const parentFolderEdits = new Map<string, Record<"css" | "other", (text: string) => Edit[]>>();
|
||||
for (let at = oldPath.indexOf("/"); at > 0; at = oldPath.indexOf("/", at + 1)) {
|
||||
const dir = oldPath.slice(0, at);
|
||||
const inside = existing
|
||||
.filter((path) => path.startsWith(`${dir}/`))
|
||||
.map((path) => path.slice(at + 1));
|
||||
parentFolderEdits.set(
|
||||
dir,
|
||||
referenceEdits(
|
||||
oldPath.slice(at + 1),
|
||||
posix.relative(dir, newPath),
|
||||
isDirectory,
|
||||
inside,
|
||||
true,
|
||||
),
|
||||
);
|
||||
const [from, to] = [oldPath.slice(at + 1), posix.relative(dir, newPath)];
|
||||
parentFolderEdits.set(dir, {
|
||||
css: referenceEdits(from, to, isDirectory, inside, { targets: null }),
|
||||
other: referenceEdits(from, to, isDirectory, inside, { targets: new Set(inside) }),
|
||||
});
|
||||
}
|
||||
let updatedCount = 0;
|
||||
for (const file of textFiles) {
|
||||
@@ -833,7 +840,8 @@ function updateReferences(
|
||||
const content = readableText(file);
|
||||
if (content === null) continue;
|
||||
|
||||
const beside = parentFolderEdits.get(relative(projectDir, dirname(file)).split(sep).join("/"));
|
||||
const parent = parentFolderEdits.get(relative(projectDir, dirname(file)).split(sep).join("/"));
|
||||
const beside = parent?.[/\.css$/i.test(file) ? "css" : "other"];
|
||||
// A bare path in a folder's file is relative to that folder, so its edit wins where both start.
|
||||
const updated = applyEdits(content, [...(beside?.(content) ?? []), ...rootEdits(content)]);
|
||||
if (updated !== content) {
|
||||
|
||||
Reference in New Issue
Block a user