From 055fbf3418275d065f16339e5fa0f9fd81852e6a Mon Sep 17 00:00:00 2001 From: Akshar Patel Date: Sat, 24 Oct 2026 12:32:58 -0400 Subject: [PATCH] fix(cli): emit valid JSON for filtered keyframes --- docs/developers/cli.mdx | 4 ++++ packages/cli/src/commands/keyframes.test.ts | 24 ++++++++++++++++++++- packages/cli/src/commands/keyframes.ts | 17 ++++++++------- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/docs/developers/cli.mdx b/docs/developers/cli.mdx index 9ec21a74861..cf3065fafa1 100644 --- a/docs/developers/cli.mdx +++ b/docs/developers/cli.mdx @@ -42,6 +42,10 @@ remains authoritative: run `npx hyperframes --help`. `hyperframes timeline` also accepts agent editing verbs. Use `move`, `trim`, `split`, or `delete` with a row ref; add `--plan` to inspect the before/after rows and diff without writing, or `--json` for a machine-readable receipt. Time values accept seconds, relative offsets, frames, and anchors such as `after:#intro`. +For scripts, `npx hyperframes keyframes --runtime css --json` writes one JSON +result to stdout. Use `--runtime anime` for Anime.js. The static authoring +notice appears only in human-readable output. + ## Rendering and automation The CLI also renders in batches and from variables, writes image sequences and diff --git a/packages/cli/src/commands/keyframes.test.ts b/packages/cli/src/commands/keyframes.test.ts index 7f717e36941..139d0ada6ba 100644 --- a/packages/cli/src/commands/keyframes.test.ts +++ b/packages/cli/src/commands/keyframes.test.ts @@ -9,7 +9,8 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { beforeAll, describe, expect, it, onTestFinished } from "vitest"; +import { beforeAll, describe, expect, it, onTestFinished, vi } from "vitest"; +import { runCommand } from "citty"; import type { ArgsDef } from "citty"; import { ensureDOMParser } from "../utils/dom.js"; import keyframesCommand from "./keyframes.js"; @@ -38,6 +39,27 @@ beforeAll(() => ensureDOMParser()); const wrap = (script: string) => `
`; +describe("keyframes JSON output", () => { + it.each(["css", "anime"])("emits parseable JSON with --runtime %s", async (runtime) => { + const dir = mkdtempSync(join(tmpdir(), "hf-keyframes-json-")); + onTestFinished(() => rmSync(dir, { recursive: true, force: true })); + const source = join(dir, "index.html"); + writeFileSync(source, wrap("")); + const log = vi.spyOn(console, "log").mockImplementation(() => {}); + onTestFinished(() => log.mockRestore()); + + await runCommand(keyframesCommand, { rawArgs: [source, "--runtime", runtime, "--json"] }); + + const output = log.mock.calls.map((args) => args.join(" ")).join("\n"); + const result: unknown = JSON.parse(output); + expect(result).toMatchObject({ + project: "index.html", + runtime, + compositions: [{ composition: "index.html", tweens: [], cssKeyframes: [], anime: [] }], + }); + }); +}); + describe("keyframes direct composition scope", () => { it("keeps the project root and passes the nested HTML entry to --shot", () => { const projectDir = mkdtempSync(join(tmpdir(), "hf-keyframes-target-")); diff --git a/packages/cli/src/commands/keyframes.ts b/packages/cli/src/commands/keyframes.ts index ca55dd86272..2fb22be347a 100644 --- a/packages/cli/src/commands/keyframes.ts +++ b/packages/cli/src/commands/keyframes.ts @@ -1085,14 +1085,6 @@ function createKeyframesCommand(options: Partial = {}) async run({ args }) { ensureDOMParser(); const runtime = normalizeRuntime(args.runtime, commandOptions.defaultRuntime); - if (runtime === "css" || runtime === "anime") { - console.log( - c.dim( - `${commandOptions.name}: ${runtime} output is a static authoring surface; use validate/render/snapshot to verify runtime adapter seekability.`, - ), - ); - console.log(); - } const { comps: rawComps, allComps, projectName, projectDir, entryFile } = resolveScope(args); const comps = filterCompositionsByRuntime(rawComps, runtime); @@ -1107,6 +1099,15 @@ function createKeyframesCommand(options: Partial = {}) return; } + if (runtime === "css" || runtime === "anime") { + console.log( + c.dim( + `${commandOptions.name}: ${runtime} output is a static authoring surface; use validate/render/snapshot to verify runtime adapter seekability.`, + ), + ); + console.log(); + } + const total = comps.reduce( (n, cmp) => n + cmp.tweens.length + cmp.cssKeyframes.length + cmp.anime.length, 0,