Skip to content

fix(cli): resolve sub-composition <audio> src relative to its own file#489

Merged
miguel-heygen merged 1 commit intoheygen-com:mainfrom
MuTsunTsai:fix/lint-audio-src-subcomp-path
Apr 25, 2026
Merged

fix(cli): resolve sub-composition <audio> src relative to its own file#489
miguel-heygen merged 1 commit intoheygen-com:mainfrom
MuTsunTsai:fix/lint-audio-src-subcomp-path

Conversation

@MuTsunTsai
Copy link
Copy Markdown
Contributor

What

lintAudioSrcNotFound was reporting audio_src_not_found errors for <audio> elements inside sub-compositions whose src attribute starts with ../, even when the file actually exists at the path the bundler rewrites to.

Why

The pre-render lint check is currently produced by reading every HTML source (root + each compositions/*.html) into a single string array, then for each <audio src> doing resolve(projectDir, src) and checking the filesystem. That's correct for the root index.html but wrong for sub-compositions:

  • A sub-composition at compositions/scene.html writes <audio src="../assets/foo.mp3"> because its bundler rewrites paths relative to the sub-composition's own file (see rewriteAssetPath in packages/core/src/compiler/rewriteSubCompPaths.ts).
  • resolve(projectDir, "../assets/foo.mp3") walks one level above the project — to {parentOfProject}/assets/foo.mp3 — which does not exist, so the lint check reports the file as missing.

Net effect: a project that follows the documented authoring model (sub-composition assets written relative to the sub-composition file) gets a hard audio_src_not_found error from hyperframes lint, even though hyperframes preview and hyperframes render both work correctly.

How

  1. Replace the bare string[] of htmlSources with HtmlSource[] = { html, compSrcPath? }. compSrcPath is the data-composition-src value (e.g. compositions/scene.html); the root index.html leaves it undefined.

  2. Inside lintAudioSrcNotFound, when a sub-composition source's src matches the existing rewrite criteria, run it through the bundler's own rewriteAssetPath(compSrcPath, src) helper to convert ../assets/foo.mp3 into assets/foo.mp3. Then resolve(projectDir, ...) against the rewritten path.

  3. Keep the original (un-rewritten) src in the missingSrcs array so the finding message and fix hint remain greppable from the author's HTML — they should see what they wrote, not the rewritten internal form.

  4. Update lintProjectAudioFiles and lintDuplicateAudioTracks signatures to accept the same HtmlSource[], since they read off the same array. Their internal logic doesn't depend on compSrcPath, but the type has to match.

rewriteAssetPath is already exported from @hyperframes/core (used by the producer/preview bundlers). No new dependency, no duplication of path-rewrite logic.

Test plan

  • bunx oxlint packages/cli/src/utils/lintProject.ts packages/cli/src/utils/lintProject.test.ts — clean
  • bunx oxfmt --check packages/cli/src/utils/lintProject.ts packages/cli/src/utils/lintProject.test.ts — clean
  • bun run --filter @hyperframes/cli typecheck — clean
  • bunx vitest run src/utils/lintProject.test.ts (in packages/cli) — 39 tests pass (37 previous + 2 new)
  • Two new tests added to lintProject.test.ts:
    • resolves sub-composition src relative to the sub-composition file (../assets/...) — sub-comp at compositions/captions.html referencing ../assets/bgm.mp3 with the file present at {project}/assets/bgm.mp3 → no audio_src_not_found finding
    • flags sub-composition src that resolves to a missing file via ../ — same shape but no file on disk → finding raised, message contains the original ../assets/... src so authors can grep it
  • Verified locally by hand-patching the bundled dist/cli.js in a real project with a sub-composition that references nine <audio> elements via ../assets/narration/*.mp3. Before the patch: 9 hard errors despite all files existing and render succeeding. After the patch: lint clean for those audio sources. Renaming one of the mp3s on disk still produces the expected single error pointing at the un-rewritten path, so the rule still catches genuine misses.

Non-goals

  • Not changing the bundler's path-rewrite rules. This PR uses rewriteAssetPath as the source of truth — same behaviour, same edge cases.
  • Not changing the audio_src_not_found finding message format beyond what already shows up. Authors continue to see the literal src they wrote.

`lintAudioSrcNotFound` resolves every `<audio src>` against the project
root, which is correct for index.html but wrong for sub-compositions —
their srcs are written relative to the sub-composition file (e.g.
`../assets/foo.mp3`), and the bundler rewrites them at compile time.

Carry the sub-composition path alongside each html source and run
`<audio src>` strings starting with `../` through the existing
`rewriteAssetPath` helper before checking existence on disk. Mirrors the
runtime/bundler behaviour so the lint check sees the same path the
renderer will fetch.

Original (un-rewritten) src is still surfaced in the finding message so
authors can grep for it in their HTML.
@miguel-heygen miguel-heygen self-requested a review April 25, 2026 14:35
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes a real lint false positive for sub-composition audio paths. I reproduced the base behavior (../assets/bgm.mp3 was reported missing even though {project}/assets/bgm.mp3 existed), verified the head removes only that false positive while preserving the missing-file error, and checked the changed-file lint/format/unit tests plus green CI on the live head.

@miguel-heygen miguel-heygen merged commit 1782ceb into heygen-com:main Apr 25, 2026
19 checks passed
@MuTsunTsai MuTsunTsai deleted the fix/lint-audio-src-subcomp-path branch April 25, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants