The set-version guard parsed `git status --porcelain` and extracted the path with a fixed `line.slice(3)`. The porcelain "XY <path>" prefix width can shift, and when it did the slice dropped a leading character — misreading `.claude-plugin/plugin.json` as `claude-plugin/plugin.json`, which failed the allowed-paths match and falsely blocked a legitimate release with "Unexpected uncommitted changes". There was no escape hatch. Collect changed paths from `git diff --name-only -z HEAD` (tracked) plus `git ls-files --others --exclude-standard -z` (untracked) instead. Both emit bare NUL-separated repo-relative paths with no status column to misparse, so the allowed-paths comparison is exact. Extract the pure helpers (splitNulList, findUnexpectedChanges) and cover them with tests. Also document the release flow in CLAUDE.md (the repo had no release docs).
5.0 KiB
Hyperframes
Open-source video rendering framework: write HTML, render video.
packages/
cli/ → hyperframes CLI (create, preview, lint, render)
core/ → Types, parsers, generators, linter, runtime, frame adapters
engine/ → Seekable page-to-video capture engine (Puppeteer + FFmpeg)
player/ → Embeddable <hyperframes-player> web component
producer/ → Full rendering pipeline (capture + encode + audio mix)
studio/ → Browser-based composition editor UI
Development
bun install # Install dependencies
bun run build # Build all packages
bun run test # Run tests
This repo uses bun, not pnpm. Do NOT run pnpm install — it creates a pnpm-lock.yaml that should not exist. Workspace linking relies on bun's resolution from "workspaces" in root package.json.
Linting & Formatting
This project uses oxlint and oxfmt (not biome, not eslint, not prettier).
bunx oxlint <files> # Lint
bunx oxfmt <files> # Format (write)
bunx oxfmt --check <files> # Format (check only, used by pre-commit hook)
Always run both on changed files before committing. The lefthook pre-commit hook runs bunx oxlint and bunx oxfmt --check automatically.
Adding CLI Commands
When adding a new CLI command:
- Define the command in
packages/cli/src/commands/<name>.tsusingdefineCommandfrom citty - Export
examplesin the same file —export const examples: Example[] = [...](importExamplefrom./_examples.js). These are displayed by--help. - Register it in
packages/cli/src/cli.tsundersubCommands(lazy-loaded) - Add to help groups in
packages/cli/src/help.ts— add the command name and description to the appropriateGROUPSentry. Without this, the command won't appear inhyperframes --helpeven though it works. - Document it in
docs/packages/cli.mdx— add a section with usage examples and flags. - Validate by running
npx tsx packages/cli/src/cli.ts --help(command appears in the list) andnpx tsx packages/cli/src/cli.ts <name> --help(examples appear).
Regression Test Golden Baselines (producer)
packages/producer/tests/<name>/output/output.mp4 baselines MUST be generated
inside Dockerfile.test, not on your host. CI renders inside that Docker image
with a specific Chrome + ffmpeg build; pixel-level output drifts across
different host Chrome/ffmpeg versions and will fail PSNR at dozens of
checkpoints even when the code is correct.
# Build the test image once:
docker build -t hyperframes-producer:test -f Dockerfile.test .
# Generate or update a baseline (runs the harness with --update inside Docker):
bun run --cwd packages/producer docker:test:update <test-name>
Never run bun run --cwd packages/producer test:update directly from the
host to capture a baseline that will be committed — the resulting output.mp4
will not match CI. Use it only for local-only experimentation.
Releasing
All eight packages share one version. .github/workflows/publish.yml publishes
them when a v* tag is pushed (it also accepts a manual workflow_dispatch with
a version input). The CLI publishes as the unscoped hyperframes package; the
other seven as @hyperframes/*.
Cut a release from main:
bun run release:prepare <version> # e.g. 0.6.72
Run it twice:
- First run writes
releases/v<version>.mdand prepends an entry todocs/changelog.mdx, then stops with a non-zero exit. Edit both files: write the 1–2 sentence summary and remove the<!-- TODO -->marker. - Second run (after the TODO is gone) bumps every package and plugin
manifest, creates the
chore: release v<version>commit, and tagsv<version>(lightweight).
Then push to trigger the publish:
git push origin main --tags
Notes:
- Pre-release versions (
0.6.72-alpha.1) publish to the matching npm dist-tag (alpha) instead oflatest. --skip-changelog-checkskips the changelog gate for emergency releases.- The publish step skips any version already on npm, so re-running a failed workflow run is safe.
Skills
Composition authoring (not repo development) is guided by skills installed via npx skills add heygen-com/hyperframes. See skills/ for source. Invoke /hyperframes, /hyperframes-cli, /hyperframes-registry, /tailwind, or /gsap when authoring compositions. Use /tailwind for projects created with hyperframes init --tailwind so agents follow the pinned Tailwind v4 browser-runtime contract instead of Studio's Tailwind v3 setup. Use /animejs, /css-animations, /lottie, /three, or /waapi when a composition uses those first-party runtime adapters. Invoke /hyperframes-media for asset preprocessing (TTS narration, audio/video transcription, background removal for transparent overlays) — these commands have their own skill so the CLI skill stays focused on the dev loop. When a user provides a website URL and wants a video, invoke /website-to-hyperframes — it runs the full 7-step capture-to-video pipeline.