mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
fix(cli): check and snapshot keep their record while another app has the history open (#5845)
* fix(cli): check and snapshot keep their record while another app has the history open * test(cli): a held history no longer drops the snapshot record
This commit is contained in:
@@ -71,7 +71,7 @@ it("only warns when the report cannot be recorded; the exit status is the check'
|
||||
}
|
||||
});
|
||||
|
||||
it("still reports the check when another process holds history, without waiting for it", async () => {
|
||||
it("records the check when another process holds history, without waiting for it", async () => {
|
||||
const project = mkdtempSync(join(tmpdir(), "hf-check-history-held-"));
|
||||
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
|
||||
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
||||
@@ -90,12 +90,10 @@ it("still reports the check when another process holds history, without waiting
|
||||
try {
|
||||
await runCommand(check, { rawArgs: [project, "--json"] });
|
||||
const output = JSON.parse(String(log.mock.calls.at(-1)?.[0])) as CheckReport;
|
||||
expect(output).not.toHaveProperty("saved");
|
||||
expect(output).toHaveProperty("saved.id");
|
||||
expect(output.browserSkipped).toBe(true);
|
||||
expect(consumeCommandResult().exitCode).toBe(checkExitCode(output));
|
||||
expect(
|
||||
warn.mock.calls.some((call) => String(call[0]).includes("Check report was not recorded")),
|
||||
).toBe(true);
|
||||
expect(warn.mock.calls.flat().join("\n")).not.toContain("not recorded");
|
||||
} finally {
|
||||
historyDeps.ownerWaitMs = previousWait;
|
||||
await held.close();
|
||||
|
||||
@@ -152,7 +152,7 @@ describe("snapshot contact sheet failures", () => {
|
||||
expect(console.error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps the captured frames when another process holds history", async () => {
|
||||
it("keeps and records the captured frames when another process holds history", async () => {
|
||||
const args = {
|
||||
rawArgs: [
|
||||
project,
|
||||
@@ -181,12 +181,13 @@ describe("snapshot contact sheet failures", () => {
|
||||
expect(readFileSync(frame)).toEqual(Buffer.from("replaced PNG"));
|
||||
}
|
||||
const after = JSON.parse(readFileSync(statePath, "utf8"));
|
||||
expect(after.snapshots.unversioned).toEqual([]);
|
||||
expect(after.snapshots.latest).toBeNull();
|
||||
expect(console.error).not.toHaveBeenCalled();
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Snapshots were not recorded"),
|
||||
expect(after.snapshots.unversioned.map((record: { image: string }) => record.image)).toEqual(
|
||||
["0.5", "1"].map((time, index) =>
|
||||
join(project, "snapshots", `frame-0${index}-at-${time}s.png`),
|
||||
),
|
||||
);
|
||||
expect(console.error).not.toHaveBeenCalled();
|
||||
expect(console.warn).not.toHaveBeenCalledWith(expect.stringContaining("not recorded"));
|
||||
} finally {
|
||||
historyDeps.ownerWaitMs = previousWait;
|
||||
await held.close();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import type { AddressInfo } from "node:net";
|
||||
import { join } from "node:path";
|
||||
import { join, resolve } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createAdaptorServer } from "@hono/node-server";
|
||||
import {
|
||||
createStudioApi,
|
||||
@@ -82,6 +84,43 @@ it("does not assign results when inputs change during capture", async () => {
|
||||
expect(f.state().snapshots.latest?.revision).toBeNull();
|
||||
});
|
||||
|
||||
it("a check run while the app keeps the history open is still recorded, never under an unfiled revision", async () => {
|
||||
const f = await fixture();
|
||||
// Held here as the desktop app holds every open project's history; the CLI runs as the app runs it, a child.
|
||||
const held = await openProjectHistory({
|
||||
projectDir: f.dir,
|
||||
historyRoot: historyDeps.historyRoot,
|
||||
undoScope: "everyone",
|
||||
});
|
||||
const cli = resolve(fileURLToPath(import.meta.url), "..", "..", "cli.ts");
|
||||
const checkFromChild = () => {
|
||||
const run = spawnSync("bun", ["run", cli, "check", "--json", f.dir], {
|
||||
encoding: "utf-8",
|
||||
env: {
|
||||
...process.env,
|
||||
HYPERFRAMES_HISTORY_ROOT: historyDeps.historyRoot,
|
||||
HYPERFRAMES_SKIP_UPDATE_CHECK: "1",
|
||||
HYPERFRAMES_NO_TELEMETRY: "1",
|
||||
},
|
||||
});
|
||||
expect(run.stderr).not.toContain("not recorded");
|
||||
};
|
||||
try {
|
||||
writeFileSync(join(f.dir, "index.html"), "<html>filed</html>");
|
||||
await held.checkpoint();
|
||||
checkFromChild();
|
||||
expect(f.state().currentRevision).toBeTruthy();
|
||||
expect(f.state().checks.latest?.revision).toBe(f.state().currentRevision);
|
||||
|
||||
writeFileSync(join(f.dir, "index.html"), "<html>not filed yet</html>");
|
||||
checkFromChild();
|
||||
expect(f.state().checks.latest?.revision).toBeNull();
|
||||
expect(f.state().checks.unversioned).toHaveLength(1);
|
||||
} finally {
|
||||
await held.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("posts a preview checkpoint instead of sending GET", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "hf-artifact-preview-"));
|
||||
roots.push(dir);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { beginArtifactCapture } from "@hyperframes/studio-server/history";
|
||||
import { HistoryBusyError, beginArtifactCapture } from "@hyperframes/studio-server/history";
|
||||
import { withOwner } from "./historyOwner.js";
|
||||
|
||||
const SKIP_BUSY_HISTORY = { ownerWaitMs: 0 };
|
||||
|
||||
function unlessAnotherProcessKeepsTheHistory(error: unknown): void {
|
||||
if (!(error instanceof HistoryBusyError)) throw error;
|
||||
}
|
||||
|
||||
export async function captureArtifactRevision(projectDir: string) {
|
||||
return withOwner(
|
||||
projectDir,
|
||||
async (owner) => {
|
||||
await owner.checkpoint();
|
||||
return beginArtifactCapture(projectDir);
|
||||
},
|
||||
SKIP_BUSY_HISTORY,
|
||||
await withOwner(projectDir, (owner) => owner.checkpoint(), SKIP_BUSY_HISTORY).catch(
|
||||
unlessAnotherProcessKeepsTheHistory,
|
||||
);
|
||||
return beginArtifactCapture(projectDir);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user