mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 17:27:09 +00:00
fix(cli): stop check --at false alarms and name a stale CLI version (#4417)
* fix(cli): stop check --at failing still frames as a frozen timeline * fix(cli): warn when the running CLI is older than the project's pin * fix(cli): keep judging spread samples when check --at is combined with other gates
This commit is contained in:
@@ -1479,6 +1479,29 @@ describe("check pipeline", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("does not flag --at times the user picked on a still end card", async () => {
|
||||
const driver = fakeDriver({
|
||||
getDuration: vi.fn(async () => 53.7),
|
||||
collectLayoutGeometry: vi.fn(async () => "frozen"),
|
||||
});
|
||||
const { report } = await runScenario(driver, { at: [51, 52.5] });
|
||||
|
||||
expect(report.layout.samples).toEqual([51, 52.5]);
|
||||
expect(report.layout.findings.some((finding) => finding.code === "sweep_static")).toBe(false);
|
||||
});
|
||||
|
||||
it("still judges the spread samples --at-transitions adds to an --at run", async () => {
|
||||
const driver = fakeDriver({
|
||||
getDuration: vi.fn(async () => 53.7),
|
||||
getTransitionBoundaries: vi.fn(async () => [10, 20]),
|
||||
collectLayoutGeometry: vi.fn(async () => "frozen"),
|
||||
});
|
||||
const { report } = await runScenario(driver, { at: [51, 52.5], atTransitions: true });
|
||||
|
||||
expect(report.layout.samples).toEqual([10, 15, 20, 51, 52.5]);
|
||||
expect(report.layout.findings.some((finding) => finding.code === "sweep_static")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not flag intentional static content declared with data-no-timeline", async () => {
|
||||
const driver = fakeDriver({
|
||||
getDuration: vi.fn(async () => 6),
|
||||
|
||||
@@ -110,6 +110,8 @@ function buildMotionSampleTimes(duration: number): number[] {
|
||||
interface SampleGrid {
|
||||
duration: number;
|
||||
layoutSamples: number[];
|
||||
/** `--at` times: they can all land on a still stretch, so the frozen-sweep guard never judges them. */
|
||||
userPickedSamples: number[];
|
||||
captionSamples: number[];
|
||||
frameSamples: number[];
|
||||
transitionSamples: number[];
|
||||
@@ -158,6 +160,7 @@ async function buildSampleGrid(
|
||||
return {
|
||||
duration,
|
||||
layoutSamples,
|
||||
userPickedSamples: options.at?.length ? baseSamples : [],
|
||||
captionSamples,
|
||||
frameSamples,
|
||||
transitionSamples: transitions.times,
|
||||
@@ -213,7 +216,7 @@ interface GridSamples {
|
||||
screenshots: CheckScreenshot[];
|
||||
contrastMs: number;
|
||||
/** One geometry+opacity fingerprint per layout sample (#U10 frozen-sweep guard). */
|
||||
geometrySignatures: string[];
|
||||
geometrySignatures: { time: number; signature: string }[];
|
||||
/** Every rotatable element's geometry at each layout sample; grouped by
|
||||
* selector after the run to detect rotation_pivot_drift. */
|
||||
rotationSamples: RotationSample[];
|
||||
@@ -417,7 +420,7 @@ async function collectGridSamples(
|
||||
const layoutIssues = await driver.collectLayout(time, options.tolerance, options.layout);
|
||||
collected.layoutIssues.push(...layoutIssues);
|
||||
issuesAtTime.push(...layoutIssues);
|
||||
collected.geometrySignatures.push(await driver.collectLayoutGeometry());
|
||||
collected.geometrySignatures.push({ time, signature: await driver.collectLayoutGeometry() });
|
||||
collected.rotationSamples.push(...(await driver.collectRotationSample(time)));
|
||||
collected.indicatorFrames.push(await driver.collectOffPivotRotationSample(time));
|
||||
}
|
||||
@@ -1089,9 +1092,12 @@ export async function runAuditGrid(
|
||||
);
|
||||
motionIssues = [...motionIssues, ...(await driver.anchorMotionIssues(evaluated))];
|
||||
}
|
||||
const userPicked = new Set(grid.userPickedSamples);
|
||||
const sweepFindings = detectSweepStatic(
|
||||
grid.duration,
|
||||
collected.geometrySignatures,
|
||||
collected.geometrySignatures
|
||||
.filter((sample) => !userPicked.has(sample.time))
|
||||
.map((sample) => sample.signature),
|
||||
motionIssues,
|
||||
await driver.hasNoTimelineDeclaration(),
|
||||
);
|
||||
|
||||
@@ -8,6 +8,12 @@ import { join } from "node:path";
|
||||
// "./updateCheck.js" would only override the export binding, not the internal
|
||||
// call printStalePinNotice makes to it from within the same module.
|
||||
let store: Record<string, unknown> = {};
|
||||
let runningVersion = "0.7.55";
|
||||
vi.mock("../version.js", () => ({
|
||||
get VERSION() {
|
||||
return runningVersion;
|
||||
},
|
||||
}));
|
||||
// isDevMode() is true under vitest (module path ends in .ts), which would
|
||||
// suppress the notice unconditionally — mock ./env.js like updateCheck.test.ts does.
|
||||
vi.mock("./env.js", () => ({ isDevMode: () => false }));
|
||||
@@ -27,6 +33,7 @@ describe("printStalePinNotice", () => {
|
||||
const origWrite = process.stderr.write.bind(process.stderr);
|
||||
beforeEach(() => {
|
||||
store = { latestVersion: "0.7.55" };
|
||||
runningVersion = "0.7.55";
|
||||
writes = [];
|
||||
dir = mkdtempSync(join(tmpdir(), "hf-pin-"));
|
||||
process.stderr.write = ((s: unknown) => {
|
||||
@@ -62,6 +69,21 @@ describe("printStalePinNotice", () => {
|
||||
expect(writes.join("")).toBe("");
|
||||
});
|
||||
|
||||
it("warns on every run when an older CLI than the pin is running", () => {
|
||||
store = { latestVersion: "0.8.71" };
|
||||
runningVersion = "0.7.71";
|
||||
writeFileSync(
|
||||
join(dir, "package.json"),
|
||||
JSON.stringify({ scripts: { check: "npx --yes hyperframes@0.8.66 check" } }),
|
||||
);
|
||||
printStalePinNotice(dir);
|
||||
printStalePinNotice(dir);
|
||||
const mismatch = writes.filter((w) => w.includes("This is hyperframes 0.7.71"));
|
||||
expect(mismatch.length).toBe(2);
|
||||
expect(mismatch[0]).toContain("npx hyperframes@0.8.66");
|
||||
expect(writes.join("")).not.toContain("upgrade --project");
|
||||
});
|
||||
|
||||
it("silent under CI", () => {
|
||||
process.env.CI = "true";
|
||||
writeFileSync(
|
||||
|
||||
@@ -197,21 +197,17 @@ export function printUpdateNotice(): void {
|
||||
const STALE_PIN_THROTTLE_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Actionable, throttled notice for a project whose package.json still pins an
|
||||
* OLD hyperframes version. Unlike printUpdateNotice this DOES fire on non-TTY
|
||||
* (agents render with piped stderr) \u2014 but only when there's a concrete stale
|
||||
* pin to act on, at most once/24h per install, and never under --json/CI/dev/
|
||||
* opt-out. The whole cli.ts update block is already skipped for --json, so a
|
||||
* JSON stdout stays clean regardless.
|
||||
* Actionable notice when the running CLI is older than the project's pin (every
|
||||
* run), or when the pin is older than the latest release (once/24h per install).
|
||||
* Unlike printUpdateNotice this DOES fire on non-TTY (agents render with piped
|
||||
* stderr), but never under --json/CI/dev/opt-out: the whole cli.ts update block
|
||||
* is skipped for --json, so a JSON stdout stays clean regardless.
|
||||
*/
|
||||
export function printStalePinNotice(cwd: string = process.cwd()): void {
|
||||
if (isDevMode()) return;
|
||||
if (process.env["CI"] === "true" || process.env["CI"] === "1") return;
|
||||
if (process.env["HYPERFRAMES_NO_UPDATE_CHECK"] === "1") return;
|
||||
|
||||
const latest = getUpdateMeta().latestVersion;
|
||||
if (!latest || !isSafeVersion(latest)) return;
|
||||
|
||||
let scripts: Record<string, string> = {};
|
||||
try {
|
||||
const pkgPath = join(cwd, "package.json");
|
||||
@@ -220,7 +216,20 @@ export function printStalePinNotice(cwd: string = process.cwd()): void {
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const stale = readPinnedHyperframesVersions(scripts).filter((v) => {
|
||||
const pins = readPinnedHyperframesVersions(scripts);
|
||||
// A CLI older than the pin (e.g. a stale npx cache) misjudges every run, so this is never throttled.
|
||||
const newerPins = pins.filter((v) => isNewerSemver(v, VERSION)).sort(compareVersions);
|
||||
if (newerPins.length > 0) {
|
||||
process.stderr.write(
|
||||
`\n This is hyperframes ${VERSION}, but this project pins hyperframes@${newerPins.join(", ")}.\n` +
|
||||
` Run it through the project's npm scripts, or npx hyperframes@${newerPins.at(-1)}.\n\n`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const latest = getUpdateMeta().latestVersion;
|
||||
if (!latest || !isSafeVersion(latest)) return;
|
||||
const stale = pins.filter((v) => {
|
||||
try {
|
||||
return compareVersions(latest, v) > 0;
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user