Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/developers/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ remains authoritative: run `npx hyperframes <command> --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
Expand Down
24 changes: 23 additions & 1 deletion packages/cli/src/commands/keyframes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -38,6 +39,27 @@ beforeAll(() => ensureDOMParser());
const wrap = (script: string) =>
`<!doctype html><html><body><div id="root" data-composition-id="main" data-duration="4"><div id="dot" class="clip"></div></div><script>${script}</script></body></html>`;

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-"));
Expand Down
17 changes: 9 additions & 8 deletions packages/cli/src/commands/keyframes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1085,14 +1085,6 @@ function createKeyframesCommand(options: Partial<KeyframesCommandOptions> = {})
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);

Expand All @@ -1107,6 +1099,15 @@ function createKeyframesCommand(options: Partial<KeyframesCommandOptions> = {})
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,
Expand Down
Loading