mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-07 15:27:14 +00:00
* feat(cli): add timeline editing verbs * chore: satisfy fallow complexity gate * test(cli): cover snap rounding at known fps * test(cli): cover snap rounding at known fps * test(cli): cover snap rounding at known fps * test(cli): cover snap rounding at known fps * fix(cli): derive split ids from stable refs * fix(cli): remove timeline gate blockers * fix(cli): close freshness and output gate gaps * fix(cli): close remaining timeline review findings * refactor(cli): simplify mutation decision flow * refactor(cli): split trim and apply paths * refactor(cli): isolate trim validation * fix(cli): satisfy timeline lint gate * style: format timeline mutation files * fix(cli): preserve mutation result types * fix(cli): close timeline receipt and nested bounds gaps * fix(studio): rollback writes that fail during write * fix(cli): bound moved clips by end time
81 lines
2.8 KiB
TypeScript
81 lines
2.8 KiB
TypeScript
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import { describe, expect, it } from "vitest";
|
|
import { applyFileMutations } from "./applyFileMutations.js";
|
|
import { fileContentVersion, identifyFileWrite, resetFileWriteReceipts } from "./fileVersion.js";
|
|
|
|
function expectStaleMutation(after: string): void {
|
|
const projectDir = mkdtempSync(join(tmpdir(), "hf-mutation-version-"));
|
|
const path = join(projectDir, "index.html");
|
|
try {
|
|
writeFileSync(path, "before", "utf8");
|
|
const expectedVersion = fileContentVersion(readFileSync(path, "utf8"));
|
|
writeFileSync(path, "external", "utf8");
|
|
expect(() =>
|
|
applyFileMutations(projectDir, [
|
|
{ sourceFile: "index.html", absPath: path, before: "before", after, expectedVersion },
|
|
]),
|
|
).toThrow("file changed since the timeline was read");
|
|
expect(readFileSync(path, "utf8")).toBe("external");
|
|
} finally {
|
|
rmSync(projectDir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
describe("applyFileMutations", () => {
|
|
it("refuses a file whose version changed since the caller read it", () => {
|
|
expectStaleMutation("after");
|
|
});
|
|
|
|
it("refuses a stale no-op instead of silently accepting it", () => {
|
|
expectStaleMutation("before");
|
|
});
|
|
|
|
it("clears receipts for writes rolled back after a partial batch", () => {
|
|
resetFileWriteReceipts();
|
|
const projectDir = mkdtempSync(join(tmpdir(), "hf-mutation-rollback-"));
|
|
const first = join(projectDir, "first.html");
|
|
const second = join(projectDir, "second.html");
|
|
try {
|
|
writeFileSync(first, "first-before", "utf8");
|
|
writeFileSync(second, "second-before", "utf8");
|
|
let writes = 0;
|
|
expect(() =>
|
|
applyFileMutations(
|
|
projectDir,
|
|
[
|
|
{
|
|
sourceFile: "first.html",
|
|
absPath: first,
|
|
before: "first-before",
|
|
after: "first-after",
|
|
},
|
|
{
|
|
sourceFile: "second.html",
|
|
absPath: second,
|
|
before: "second-before",
|
|
after: "second-after",
|
|
},
|
|
],
|
|
undefined,
|
|
(path, content, encoding) => {
|
|
writes += 1;
|
|
if (writes === 2) {
|
|
writeFileSync(path, "second-partial", encoding);
|
|
throw new Error("second write failed");
|
|
}
|
|
writeFileSync(path, content, encoding);
|
|
},
|
|
),
|
|
).toThrow("second write failed");
|
|
expect(readFileSync(first, "utf8")).toBe("first-before");
|
|
expect(readFileSync(second, "utf8")).toBe("second-before");
|
|
expect(identifyFileWrite(first, fileContentVersion("first-after"))).toBeNull();
|
|
} finally {
|
|
resetFileWriteReceipts();
|
|
rmSync(projectDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|