mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
fix(cli): stage temporary HTML comparisons without recursive copies (#5286)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { basename, join } from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
buildCompareSuccessPayload,
|
||||
@@ -9,6 +9,12 @@ import {
|
||||
prepareCompareVariantProjects,
|
||||
} from "./compare.js";
|
||||
|
||||
const temporaryRoot = vi.hoisted(() => ({ path: "" }));
|
||||
vi.mock("node:os", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("node:os")>();
|
||||
return { ...actual, tmpdir: () => temporaryRoot.path || actual.tmpdir() };
|
||||
});
|
||||
|
||||
function tempDir(): string {
|
||||
return mkdtempSync(join(tmpdir(), "hf-compare-test-"));
|
||||
}
|
||||
@@ -78,6 +84,74 @@ describe("capCompareVariants", () => {
|
||||
});
|
||||
|
||||
describe("prepareCompareVariantProjects", () => {
|
||||
it("keeps earlier temporary staging folders out of later variants", () => {
|
||||
const dir = tempDir();
|
||||
const first = join(dir, "first.html");
|
||||
const second = join(dir, "second.html");
|
||||
writeFileSync(first, "<!doctype html><title>First</title>");
|
||||
writeFileSync(second, "<!doctype html><title>Second</title>");
|
||||
temporaryRoot.path = dir;
|
||||
|
||||
try {
|
||||
const prepared = prepareCompareVariantProjects([
|
||||
{ label: "First", inputPath: first, displayPath: "first.html" },
|
||||
{ label: "Second", inputPath: second, displayPath: "second.html" },
|
||||
]);
|
||||
const firstStage = prepared[0];
|
||||
const secondStage = prepared[1];
|
||||
if (!firstStage || !secondStage) throw new Error("expected two staged variants");
|
||||
expect(existsSync(join(secondStage.projectDir, basename(firstStage.projectDir)))).toBe(false);
|
||||
expect(readFileSync(join(secondStage.projectDir, "first.html"), "utf8")).toContain("First");
|
||||
expect(readFileSync(join(secondStage.projectDir, "index.html"), "utf8")).toContain("Second");
|
||||
} finally {
|
||||
temporaryRoot.path = "";
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it.each(["direct", "nested"])(
|
||||
"stages siblings when the temporary output is %s inside the source directory",
|
||||
(placement) => {
|
||||
const dir = tempDir();
|
||||
const htmlFile = join(dir, "candidate.html");
|
||||
mkdirSync(join(dir, "assets"));
|
||||
writeFileSync(htmlFile, "<!doctype html><title>Temporary candidate</title>");
|
||||
writeFileSync(join(dir, "assets", "asset.txt"), "sibling asset");
|
||||
mkdirSync(join(dir, "node_modules"));
|
||||
writeFileSync(join(dir, "node_modules", "ignored.txt"), "ignored");
|
||||
temporaryRoot.path = placement === "direct" ? dir : join(dir, "temporary");
|
||||
mkdirSync(temporaryRoot.path, { recursive: true });
|
||||
writeFileSync(join(temporaryRoot.path, "sibling.txt"), "temporary-root sibling");
|
||||
|
||||
try {
|
||||
const prepared = prepareCompareVariantProjects([
|
||||
{ label: "Temp", inputPath: htmlFile, displayPath: "candidate.html" },
|
||||
]);
|
||||
const staged = prepared[0];
|
||||
if (!staged) throw new Error("expected a staged variant");
|
||||
expect(staged.stagedDir).toBe(staged.projectDir);
|
||||
expect(readFileSync(join(staged.projectDir, "index.html"), "utf8")).toContain(
|
||||
"Temporary candidate",
|
||||
);
|
||||
expect(readFileSync(join(staged.projectDir, "assets", "asset.txt"), "utf8")).toBe(
|
||||
"sibling asset",
|
||||
);
|
||||
expect(
|
||||
readFileSync(
|
||||
join(staged.projectDir, placement === "direct" ? "" : "temporary", "sibling.txt"),
|
||||
"utf8",
|
||||
),
|
||||
).toBe("temporary-root sibling");
|
||||
expect(existsSync(join(staged.projectDir, "candidate.html"))).toBe(false);
|
||||
expect(existsSync(join(staged.projectDir, "node_modules"))).toBe(false);
|
||||
expect(readFileSync(htmlFile, "utf8")).toContain("Temporary candidate");
|
||||
} finally {
|
||||
temporaryRoot.path = "";
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("uses project directories directly and stages standalone html files as index.html", () => {
|
||||
const dir = tempDir();
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import { failCommand } from "../utils/commandResult.js";
|
||||
import { cpSync, existsSync, mkdirSync, mkdtempSync, renameSync, rmSync, statSync } from "node:fs";
|
||||
import {
|
||||
cpSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readdirSync,
|
||||
realpathSync,
|
||||
renameSync,
|
||||
rmSync,
|
||||
statSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { basename, dirname, extname, join } from "node:path";
|
||||
import { defineCommand } from "citty";
|
||||
@@ -16,6 +26,7 @@ import { trackCompareSheet } from "../telemetry/events.js";
|
||||
import { serveStaticProjectHtml } from "../utils/staticProjectServer.js";
|
||||
import { withMeta } from "../utils/updateCheck.js";
|
||||
import type { Example } from "./_examples.js";
|
||||
import { isPathInside } from "@hyperframes/parsers/asset-paths";
|
||||
|
||||
const MAX_COMPARE_VARIANTS = 16;
|
||||
const MAX_COLUMNS = 4;
|
||||
@@ -198,18 +209,39 @@ function inputError(variant: CompareVariantSpec): Error {
|
||||
);
|
||||
}
|
||||
|
||||
function stageHtmlVariant(variant: CompareVariantSpec): PreparedCompareVariant {
|
||||
function copyVariantSiblings(
|
||||
sourceDir: string,
|
||||
destinationDir: string,
|
||||
stagingPaths: readonly string[],
|
||||
): void {
|
||||
mkdirSync(destinationDir, { recursive: true });
|
||||
for (const entry of readdirSync(sourceDir)) {
|
||||
if (entry === "node_modules" || entry === ".git") continue;
|
||||
const src = join(sourceDir, entry);
|
||||
const dest = join(destinationDir, entry);
|
||||
if (stagingPaths.includes(src)) continue;
|
||||
if (stagingPaths.some((path) => isPathInside(path, src))) {
|
||||
copyVariantSiblings(src, dest, stagingPaths);
|
||||
continue;
|
||||
}
|
||||
cpSync(src, dest, {
|
||||
recursive: true,
|
||||
filter: (path) => basename(path) !== "node_modules" && basename(path) !== ".git",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function stageHtmlVariant(
|
||||
variant: CompareVariantSpec,
|
||||
existingStagedDirs: readonly string[],
|
||||
): PreparedCompareVariant {
|
||||
const stagedDir = mkdtempSync(join(tmpdir(), "hf-compare-variant-"));
|
||||
try {
|
||||
// Copy the composition's sibling files but skip heavy/irrelevant trees — a
|
||||
// variant sitting next to node_modules or .git shouldn't drag them into tmp.
|
||||
cpSync(dirname(variant.inputPath), stagedDir, {
|
||||
recursive: true,
|
||||
filter: (src) => {
|
||||
const base = basename(src);
|
||||
return base !== "node_modules" && base !== ".git";
|
||||
},
|
||||
});
|
||||
const sourceDir = realpathSync(dirname(variant.inputPath));
|
||||
const stagingPaths = [stagedDir, ...existingStagedDirs].map((path) => realpathSync(path));
|
||||
copyVariantSiblings(sourceDir, realpathSync(stagedDir), stagingPaths);
|
||||
const sourceName = basename(variant.inputPath);
|
||||
if (sourceName !== "index.html") {
|
||||
renameSync(join(stagedDir, sourceName), join(stagedDir, "index.html"));
|
||||
@@ -243,7 +275,10 @@ export function prepareCompareVariantProjects(
|
||||
continue;
|
||||
}
|
||||
if (stat.isFile() && extname(variant.inputPath).toLowerCase() === ".html") {
|
||||
prepared.push(stageHtmlVariant(variant));
|
||||
const stagedDirs = prepared.flatMap((project) =>
|
||||
project.stagedDir ? [project.stagedDir] : [],
|
||||
);
|
||||
prepared.push(stageHtmlVariant(variant, stagedDirs));
|
||||
continue;
|
||||
}
|
||||
throw inputError(variant);
|
||||
|
||||
Reference in New Issue
Block a user